Fork me on GitHub
#beginners
<
2021-08-20
>
stopa00:08:42

Hey team, I am playing with writing an implementation for https://sep.yimg.com/ty/cdn/paulgraham/bellanguage.txt?t=1595850613&amp; The first challenge, is parsing a string of bel code.

(bel-parse "(foo bar)")) -> '(foo bar)
Now, this is tantalizingly close to clojure’s read-string. However, I can’t just pass code to read-string, for two reasons: 1. Bel has a more loose description for characters. Effectively anything with a \ prepended counts as a character
(bel-parse "\abc") -> \abc (this is a "character" in Bel)
2. Bel supports writing macros, so I’ll need a way to tell clojure’s reader, to not do anything with the normal macro writing helpers
(bel-parse "`(foo ~x)") -> # TODO: need some way to get the information about "`" and "~", without clojure's reader consuming it
Because of these two issues, I guess I would have to write my own parser, and I can’t just rely on read-string (unless read-string is much more configurable than I thought). If this is the case, is there a good parser library you’d recommend in clojure?

seancorfield00:08:39

We use Instaparse at work @stopachka to turn an English-like search rule language into a data structure that we then turn in JSON to send to Elastic Search.

🔥 9
stopa00:08:37

oo — that sounds awesome, thanks @seancorfield! Will play with it

stagmoose01:08:41

I have problem getting the intermediate values with the combination of (map & recursion). I would like to add span (hiccup) according to the nested level of a vector. you could see the level result printed in the repl is correct, but how can I get the evaluation of for loop as well. If I move for loop behind the recursion, because do will evaluate to the last form and this will break the receiving value of the map function of the previous level of recursion. Should I add an external source (like atom) to save my intermediate for loop result there or there exists more elegant way to do this? (code is added in the thread)

stagmoose01:08:12

For the code:

(def text-nest [[[[]]] []])

  (defn gen-level-1 [col level]
    (if (coll? col)
      (do (prn level)
          (for [x (range level)]
            [:span "$"])
          (map #(gen-level-1 %1 (inc level)) col))))

  (gen-level-1 text-nest 0)

noisesmith17:08:55

minor note: for in clojure is not a loop, it's a lazy list comprehension

👌 3
stagmoose15:08:41

I have figured out an alternative answer:

(defn gen-level-5 [col level]
    (if (coll? col)
      (mapcat #(concat (concat (repeat level [:span]) [[:br]]) (gen-level-5 % (inc level))) col)
      []))

  (gen-level-5 text-nest 1)
;; => ([:span] [:br] [:span] [:span] [:br] [:span] [:span] [:span] [:br] [:span] [:br])
sean's answer is in the second to this message

seancorfield01:08:52

@s0930161 Screenshots are hard for us to read. Using a code snippet is easier -- and for a small piece of code like that you could just paste it in between triple backticks so it formats as code. As for the code and your problem, it's unclear what output you're expecting so it's hard to say what the code should do. Your (lazy) for in that code won't do anything because, as you figured, do returns the last expression -- the result of map.

👌 3
seancorfield01:08:42

For that input -- [[[[]]] []] -- what output do you want?

stagmoose01:08:24

Thanks for your reply! I added code to the thread of my original post. My screenshot is meant to show the repl result. [[[[]]] []] in this case translate to 0 1 2 3 1 , and I would like to get (0 indent) (1 indent) (2 ident) ... and so on, namely getting different indentation based on the nested level of the location of that vector. (I used $ to represent indentation in my code)

seancorfield02:08:53

Are you looking for something like this:

([:span 0]
 [:span 1]
 [:span 2]
 [:span 3]
 [:span 1])

seancorfield02:08:43

If so, try the following code:

(defn gen-level [coll level]
  (when (coll? coll)
    (cons [:span level]
          (mapcat #(gen-level % (inc level)) coll))))

(gen-level text-nest 0)
Happy to explain any and all of it.

seancorfield03:08:54

You don't need the for loop because you're going to recurse down into the nesting automatically.

seancorfield03:08:20

You need mapcat to "un-nest" the results and join them together instead of map.

stagmoose04:08:37

@seancorfield I believe [:span 2] will generate something like <span> 2 </span> . (correct me if I am wrong:sweat_smile:) But I need to use the number 2 to generate two item like <span></span><span></span> , which will look like having two indentation visually. So maybe I still need some looping do achieve this?

seancorfield04:08:47

Ah, OK, so instead of cons, you'd want something like (into (mapv (fn [_] [:span]) (range (inc level))) (mapcat ...))

stagmoose04:08:41

@seancorfield I'll need to loop up mapv a bit :rolling_on_the_floor_laughing: and think this through. Thanks again, Sean.

seancorfield04:08:32

I guess I still don't understand what output you're trying to generate.

seancorfield04:08:20

All you're going to get is a bunch of <span/> tags. How does that relate to the nesting? What delineates each group of spans?

seancorfield04:08:26

I generated this:

[[:span "$"] [:br]
 [:span "$"] [:span "$"] [:br]
 [:span "$"] [:span "$"] [:span "$"] [:br]
 [:span "$"] [:span "$"] [:span "$"] [:span "$"] [:br]
 [:span "$"] [:span "$"] [:br]]
just as an example of nesting and delineated spans, from this code
(ns stagmoose)

(def text-nest [[[[]]] []])

(defn gen-level [coll level]
  (when (coll? coll)
    (into (mapv (fn [_] [:span "$"]) (range (inc level)))
          (cons [:br] (mapcat #(gen-level % (inc level)) coll)))))

(gen-level text-nest 0)

🔥 3
stagmoose04:08:01

my original intention is to build something like text editor and I want to put my item inside the nested vector so `[[1 [3]] [2]]` will look like:

- 1
- - 3
- 2
so I have to decide the item's level and output the corresponding indentation to make my data look good.

stagmoose04:08:30

Am I doing it wrong or there is a better approach to this

seancorfield04:08:07

How close does my output above come to what you need?

seancorfield04:08:02

Where I have (when (coll? coll) ..) you probably want (if (coll? coll) .. [:span coll]) to generate the content maybe

seancorfield04:08:38

Like I say, it's really hard to understand what output you're trying to produce based on what you've said so far.

seancorfield04:08:03

But I think some variant of my gen-level function above is pretty close?

stagmoose04:08:15

Yes. I think I can figure it out based on your code.

seancorfield04:08:37

It's nearly 10 pm here so I'll be gone soon, but feel free to ask more Qs, either in this thread or in the channel and I'll ask them later/tomorrow!

🙏 3
stagmoose04:08:37

Sure, your help means a lot to me!

Diego Gómez02:08:02

👋 I’m here! What’d I miss?

👋 3
Dongwook Kim05:08:03

Hi, I have a question about reloading with ring. I’ve used wrap-reload for hot-reloading. It seems if file was changed, reload modified-namespaces. But when I added wrap-reload without main-entry and run it with REPL but It’s not working. I got an error “address in use”. so I added main-entry to run reloadable app and run the program using main-entry, and It’s working. Why do wrap-reload need main-entry? and How can I know it need this?

Dongwook Kim05:08:57

here is the code that is working

(ns hello-world.core
      (:require [ :refer [copy file]]
                [clojure.core :refer [bean]]
                [ring.adapter.jetty :refer [run-jetty]]
                [ring.middleware.reload :refer [wrap-reload]]
                [ring.middleware.params :refer [wrap-params]]
                [ring.middleware.multipart-params :refer [wrap-multipart-params]]
                [ring.util.response :refer [response]]))

(defn handler [{params :params}]
  (prn "test")
  (response ""))

(def reloadable-app (wrap-reload (-> handler
                                     wrap-params
                                     wrap-multipart-params)))

(defn -main []
  (run-jetty #'reloadable-app {:port 3000
                               :join? false}))

Dongwook Kim05:08:56

not working one is without (defn -main [] ()) and I loaded file in REPL

phoenixjj05:08:50

"not working one is" may be because port 3000 was already in use when you try to run from REPL. In second case it just reloads the changed file.

Dongwook Kim06:08:33

I expected same thing between REPL and main-entry like reloading changed without restart (REPL or main). I’ve just run only in REPL but same.

(ns hello-world.core
      (:require [ :refer [copy file]]
                [clojure.core :refer [bean]]
                [ring.adapter.jetty :refer [run-jetty]]
                [ring.middleware.reload :refer [wrap-reload]]
                [ring.util.response :refer [response]]))

(defn handler [{params :params}]
  (prn "test2323")
  (response ""))

(def reloadable-app
  (-> handler
      wrap-reload))

(run-jetty #'reloadable-app {:port 3000
                             :join? false})
"test"
2021-08-20 15:06:24.932:INFO:oejs.Server:qtp1546265708-28: jetty-9.4.40.v20210413; built: 2021-04-13T20:42:42.668Z; git: b881a572662e1943a14ae12e7e1207989f218b74; jvm 16.0.1+9-jvmci-21.1-b05
2021-08-20 15:06:24.944:INFO:oejs.AbstractConnector:qtp1546265708-28: Stopped ServerConnector@912a3bf{HTTP/1.1, (http/1.1)}{0.0.0.0:3000}
2021-08-20 15:06:24.950:WARN:oejs.HttpChannel:qtp1546265708-28: handleException / java.io.IOException: Failed to bind to 0.0.0.0/0.0.0.0:3000
"test123"
interesting. I realized first attempt is okay for sure. and second attempt got error as I’ve seen. and third attempt works like charm. Between attempts I didn’t do anything only request to localhost.

Oele Wapper09:08:35

sorry just learning to use slack

Oele Wapper09:08:51

I've got a function (defn xmlToString [elem params contents] and if I pass in a vector with apply, that works: (apply xmlToString [:div {:style "color: blue;"} "boembabies"]) but if I pass in a vector of vectors with apply, things break: (apply xmlToString [:div {style "color: blue;"} [:p {} "boembabies"]) then I get "; ArityException Wrong number of args (4) passed to: makeworkflow/xmlToString clojure.lang.AFn.throwArity (AFn.java:429)"

Oele Wapper10:08:00

Also: (apply xmlToString [:p {:style "color: blue"} [:div {:style "color: blue"} "boembabies"]]) Gives: ; IllegalArgumentException Don't know how to create ISeq from: clojure.lang.Keyword clojure.lang.RT.seqFrom (RT.java:542)

delaguardo10:08:16

looks like you are calling xmlToString recursively without checking its arguments. Look at the stacktrace. There should be more than one entries for xmlToString. In that case - add (prn "---DBG" elem params contents) as a first expression in the body of xmlToString to see which arguments causes that exception.

Oele Wapper10:08:34

this is the entire function: (defn xmlToString [elem params contents] "given the element name as a keyword, params as a dict, and contents" (prn "---DBG" elem params contents) (let [cont (cond (string? contents) contents (vector? contents) (join "" (map #(apply xmlToString %) contents)) :else "") kv (keyvalues params) kvs (cond (= kv "") "" :else (str " " kv))] (str "<" (name elem) kvs ">" cont "</" (name elem) ">"))) It seems the first invocation is causing this

Oele Wapper10:08:13

I think I see, so yes you need double [] around the contents

Nom Nom Mousse12:08:33

What is the idiomatic way to make the hash-map {:a [1 2] :b [3]} from [[:a 1] [:a 2] [:b 3]]?

delaguardo12:08:49

reduce

🙏 2
delaguardo13:08:05

(defn foo [args]
  (reduce
    (fn [acc [k v]]
      (update acc k (fnil conj []) v))
    {}
    args))

Alex Miller (Clojure team)13:08:02

You can also do it with conj

Alex Miller (Clojure team)13:08:20

oh, sorry, I didn't see the merging req

Nom Nom Mousse13:08:42

I'm also curious if there is an even simpler way.

(into {}
      (for [[k k+vs] (group-by first s)
            :let [v (take-nth 2 (rest (flatten k+vs)))]]
        [k v]))
Less idiomatic.

emccue13:08:00

you probably dont want flatten - mapcat identity makes more sense usually

emccue13:08:23

(let [zip-entry (fn [[k vs]]
                  (map #(vector k %) vs))]
  (vec (mapcat zip-entry m))

emccue13:08:36

I think this will work

emccue13:08:01

(vec (mapcat (fn [[k vs]] (map #(vector k %) vs)) m))

🙏 2
emccue13:08:55

oh wait I did it backwards

Nom Nom Mousse13:08:04

Thanks, will have a look at it.

Endre Bakken Stovner14:08:17

The most easy-to-understand way would probably be to turn every tuple into a map and use a merge function that adds the v to a list @ key k. Would be two simple commands. I am on a mobile now so can’t test in a REPL

Ed15:08:20

there's also the xforms library:

(require '[net.cgrand.xforms :as x])
(into {} (x/by-key first (comp (map second) (x/into []))) [[:a 1] [:a 2] [:b 3]])

quoll19:08:27

I figure that the idiomatic way is to copy the approach used in group-by, except also applying a function to the accumulated data. The result of this is almost exactly what @U04V4KLKC wrote at the top of this thread. (The one possible distinction is that this would use assoc/`get` instead of update with an fnil on the conj. This is a trivial distinction).

Oele Wapper12:08:18

When clojure gives an error message how can I get the closest expression to the error ?

Russell Mull15:08:35

Often, you can look through the stack trace to find the deepest point where your own code is involved; that should point you to a source file and line number.

Russell Mull15:08:59

If you're doing something with lazy sequences things can get a bit tricky, but that's a good starting point.

gethuen15:08:27

I am trying to learn re-frame and I am getting confused looking at the templates source

gethuen15:08:26

(defn ^:dev/after-load mount-root []
  (re-frame/clear-subscription-cache!)
  (let [root-el (.getElementById js/document "app")]
    (rdom/unmount-component-at-node root-el)
    (rdom/render [views/main-panel] root-el)))

gethuen15:08:43

I don't understand ^:dev/after-load. I believe it is a reader macro to add meta information, but I don't understand the point of meta information. who uses it/consumes it?

dpsutton15:08:29

shadow-cljs i believe:

dpsutton15:08:22

the gist is "my code has changed, react should rerender the application again with the new code"

gethuen15:08:34

but here is the init function

(defn init []
  (routes/start!)
  (re-frame/dispatch-sync [::events/initialize-db])
  (re-frame/dispatch-sync [::rp/add-keyboard-event-listener "keydown"])
  (re-frame/dispatch-sync [::bp/set-breakpoints
                           {:breakpoints [:mobile
                                          768
                                          :tablet
                                          992
                                          :small-monitor
                                          1200
                                          :large-monitor]
                            :debounce-ms 166}])
  (dev-setup)
  (mount-root))
I just don't get it. I mean here, I am expecting (mount-root) to run, and you are saying that meta information can be used to bypass that?

dpsutton15:08:39

i don't think i said that? the metadata on mount-root is what will ensure that it runs after every compiliation

gethuen15:08:16

@dpsutton I didn't mean to say you said that, I just understood you as saying that meta information can modify when a function gets called

dpsutton15:08:32

its just information. shadow runs the compiler, outputs new js, presumably syncs it over a websocket to the browser, and then calls the function that has the metadata that says to call if after loading

dpsutton15:08:55

it's not magic that somehow happens. that metdata is information to a clojure program, shadow-cljs, and indicates it should call it after loading code

gethuen16:08:48

so in this specific case, the ^:dev/after-load is a reader macro that acts like advice? https://en.wikipedia.org/wiki/Advice_(programming)

dpsutton16:08:39

no. it is just a bit of information associated with the var mount-root. It has no inherent value and doesn't affect the runtime or anything

gethuen16:08:03

@dpsutton okay, thank you for helping me understand it

dpsutton16:08:30

user=> (defn ^:special foo [] (println "I'm special"))
#'user/foo
user=> (defn bar [] (println "i'm not special"))
#'user/bar
user=> (let [f (->> (ns-publics *ns*)
                    (filter (fn [[s v]] (-> v meta :special)))
                    (first)
                    (val))]
         (f))
I'm special
nil
user=> 

dpsutton16:08:06

here i define a function with some metadata :special (and ^:special is just shorthand for {:special true}. Then i look through everything in the namespace for the first one that is special and then call it

gethuen16:08:56

@dpsuttion how do you use meta in your experience with clojure?

dpsutton16:08:16

sparingly. and i think this is a fantastic use of it here. It's a bit of a special case, but you mark a var as a function you want called. If you are a beginner metadata really isn't something you need to worry about at the moment. It has uses and is easily over-relied upon.

gethuen16:08:39

@dpsutton meta seems extremely powerful, but when I was looking at it I just thought "oh, so you can use meta to say this always returns a string" or something very simple and one-use

Oele Wapper17:08:08

if genprotobuf.clj in the current folder has a "(defn prototext ..." how do I import this? I'm trying to "(ns somens (:require genprotobuf :refer [prototext])" but this results in "; IllegalAccessError prototext does not exist clojure.core/refer (core.clj:4119)"

seancorfield18:08:27

@oelewapperke Can you paste the full stacktrace into a Gist or pastebin or something like that and drop the link in here so we can see the full information?

seancorfield17:08:49

@oelewapperke What is the ns in genprotobug.clj? The :require needs the full ns name -- also needs [..`]` around the arguments: (:require [genprotobuf :refer [prototext]])

Oele Wapper17:08:57

(ns genprotobuf (:require [clojure.string :only [join split replace]]))

Oele Wapper17:08:08

and the ns in the program file is: (ns makeit (:require [clojure.string :refer [join split split-lines]]) (:require [genprotobuf :refer [prototext]]))

seancorfield18:08:31

If you change :only to :refer in genprotobuf, does that make a difference?

popeye18:08:47

I have seen below code , which returns a key, where or when such function will be helpful?

(defn client
  []
  :clinet-key)

Russell Mull18:08:05

I'm having a hard time coming up with a good reason to do that.

Russell Mull18:08:48

Specifically having a zero-arity function for this is not likely to be useful in a way that's better than other, simpler alternatives.

Russell Mull18:08:09

For example, if for some reason you really want a global definition for this, written down in your code, you can do (def client :client-key)

Russell Mull18:08:36

But even then, I don't know what you'd DO with that. It feels pointlessly defensive.

Russell Mull18:08:28

If you wanted to have a key which is computed, you can do (defn client [param] (keyword (str "client-key-" param))) or similar. THAT would make sense.

Russell Mull18:08:52

It could potentially be useful as a testing hook, if somebody is changing it out using with-redefs. But that would be more idiomatic as (def ^:dynamic *client* :client-key) / binding, most of the time.

Russell Mull18:08:45

That last point is debatable, though, especially if it's ONLY for testing. So that may be it.

Russell Mull18:08:25

Oh here's a possibility: maybe a different part of the program deals with thunks (functions of no parameters), and the execution time really matters. Some of them might have side effects, for example:

(defn bad-client [dir]
  (fn []
    ( "rm" "-rf" dir)
    :bad-client-key))
And then they would pass in either client or (bad-client "/home/my-enemy") to the thing that will be calling the thunk.

Russell Mull18:08:03

There are many possibilities... not sure if this helps?

Ryan20:08:33

Hey all, can't tell if this is better in beginner or reagent, but here goes: Have a react component that needs JSON tree data, passed in as a prop, whats the best way to go about that?

dpsutton20:08:23

do you have the data already? is it behind an api call?

Ryan21:08:09

Step 1 is pass it static data and get the component rendering 🙂

Ryan21:08:43

for now I've def'd it and am trying to call it

Ryan21:08:57

[:> Tree {:source list-mock}]

Ryan21:08:07

the EDN is def'd to list-mock

dpsutton21:08:39

ok so if it is edn and you are using a react component like :> Tree you probably need the data to be javascript objects and not clojurescript data. And we're talking a bit loose here because EDN is a string format but i'm assuming we're actually dealing with the clojurescript values from that string

Ryan21:08:06

Yeah makes sense

Ryan21:08:40

Is this the part where I pick from one of probably 3 fairly popular libraries for doing this? or have I lucked out and there's only 1? 🙂 or is the real true way to roll my own because it's probably only 4 lines of clojurescript anyway

dpsutton21:08:38

(clj-js list-mock) should accomplish it

dpsutton21:08:45

(clj->js list-mock) sorry

Ryan21:08:44

Awesome, I always manage to pick the wrong one when there's a choice 😉

Ryan21:08:28

Awesome, I see tree data. Thanks so much!

🎉 5