Fork me on GitHub
#beginners
<
2023-03-01
>
Thanh Nguyen10:03:55

Context: I am doing Day 11 of Advent of Code. More details and question in replies. Thanks in advance.

Thanh Nguyen10:03:05

I got a string new = old + 3, and I want to create an equivalent function (fn [old] (+ old 3)). I figured it out that I need a macro, and hacking around gave me this:

(defmacro parse-condition-text
  [text]
  (let [parts (->> (str/split text #" ")
                   (map str/trim)
                   (filter (fn [part]
                             (not (= part "")))))
        [operand-1 operator-1
         operand-2 operator-2 operand-3] parts]
    `(fn* [~'old]
       (~(read-string operator-2)
         ~(read-string operand-2)
         ~(read-string operand-3)))
    ))
My problem is that the macro works fine if I pass a string into it
(-> (parse-condition-text "new = old * old")
    (quote)
    (macroexpand) ;; (fn* [old] (* old old))
    ;;(apply [5])
    )
But it does not work if I put a more complex form:
(parse-condition-text (identity "new = old + 3"))
As the REPL replies me with:
Unexpected error (ClassCastException) macroexpanding parse-operation-text at (src/day_11.clj:65:1).
class clojure.lang.PersistentList cannot be cast to class java.lang.CharSequence (clojure.lang.PersistentList is in unnamed module of loader 'app'; java.lang.CharSequence is in module java.base of loader 'bootstrap')
I figured out that the evaluation order of a macro is not as "normal", so I have to do something more, but I got kinda stuck here. Can I have some suggestion on a better macro, or a better way to do this?

Thanh Nguyen10:03:32

Another solution of mine that does not work:

(defn parse-operation-text-2
  [text]
  (let [parts (->> (str/split text #" ")
                   (map str/trim)
                   (filter (fn [part]
                             (not (= part "")))))
        [operand-1 operator-1
         operand-2 operator-2 operand-3] parts]
    (fn [old]
       ((read-string operator-2)
         (read-string operand-2)
         (read-string operand-3)))
    ))
Evaluating this always give me the value of operand-3.

delaguardo10:03:04

do you really need a macro?

delaguardo10:03:03

macro treats arguments as data: (parse-operation-text (identity "...")) will get (identity "...") as an argument text which will fail on attempt to call string operations with it

delaguardo10:03:58

parse-operation-text-2 doesn't work because you are missing backtick operator. ~ can work only inside of ` form

Thanh Nguyen10:03:10

@U04V4KLKC thank you for your answers. I knew that macro may be overkill for most stuff, and in real life I will hard code this, but I want to explore Clojure's power

delaguardo10:03:32

no need to hard code šŸ™‚ you can parse and turn the string to function in runtime.

Thanh Nguyen11:03:54

@U04V4KLKC I edited my second function. Can you have a look again

Thanh Nguyen11:03:23

It is a really nice way to solve the problem. Thank you @U04V4KLKC

šŸ‘ 1
Markus12:03:10

Hello yā€™all! Some of you may have already read about my continuous struggle with regex optimization and simplification. After a lot of trial and error I stumbled upon ā€˜term rewritingā€™ and in order to better understand which rules I need I started making a list of input rules with the desired output in a map like this.

(def rules
  {[:* [:* 't]] [:* 't]
   [:+ [:+ 't]] [:+ 't]
   [:? [:? 't]] [:? 't]
   [:? [:+ 't]] [:* 't]
   [:+ [:? 't]] [:* 't]})
The thing I noticed after writing it down is, that it looks a lot like Prolog and that the behavior I need is exactly what prolog would do with these rules, which is ā€˜check if the specified part matches and subsituteā€™ where t should be allowed to have any type. Now, I know that some rules can be implemented without logic programming, as I already did that for those shown above, but since Iā€™m working with tree structures some more complicated rules would result in very difficult to read and maintain code, as there would be a lot of moving around in the tree with zipper and the likes of it. My question now is, is it feasible to implement this in a simple manner using something like core.logic? The trees Iā€™m working with consist of keywords and strings and are structured in vectors if that helps with pointing me to the right place. Any kind of help is very much appreciated!

delaguardo12:03:19

looks like a job for meander https://github.com/noprompt/meander there is also #meander channel where you can get some tips about specific use case

Markus12:03:55

@U04V4KLKC That looks really interesting! Thanks for suggesting that, I will definitely take a closer look!

delaguardo12:03:56

specifically this blog - https://jimmyhmiller.github.io/meander-rewriting might be a good start

1
fenton12:03:18

(trying this again, my repo was private before, oops) Quick instructions on setting up a project for logging with logback and SLF4j (everything all in one place in 82 words (- xml file config :)). https://github.com/ftravers/dotfiles/blob/master/homedir/org-roam/clojure.md#logback--slf4j

Rambabu Patina12:03:19

Hi Guys, I am new to use Fulcro. I have a scenario like on button click first I need to send an event to third party api and then secondly close the modal dialog. Here is the code

:onClick #(comp/transact! this [(send-event!) (close-dialog!)] {:optimistic? false})}
The events are sending properly from first call but not reaching the second call (i.e close-dialog!). The send-event defmutation is like
(m/defmutation send-event! [{:keys []}]
  (remote [{:keys [app state]}]
    (let [{:keys [plg-data module]} (comp/shared app) 
        ....
I posted a question in #C68M60S4F channel as well. Any idea what I am missing?

Nick14:03:37

Hi All -- I am trying to write a function that lazily converts a CSV to CSV.gz . This needs to be lazy as the CSV is too large to fit in memory. I have the following parts running working in a repl : 1. A function to Gzip something

(defn gzip
  "compress data.
    input: something which can be copied from by io/copy (e.g. filename ...).
    output: something which can be opend by io/output-stream.
        The bytes written to the resulting stream will be gzip compressed."
  [input output & opts]
  (with-open [output (-> output io/output-stream GZIPOutputStream.)]
    (apply io/copy input output opts)))
2. A function to Lazily Copy a CSV to CSV (from /clojure/data.csv/)
(defn copy-csv [from to]
  (with-open [reader (io/reader from)
              writer (io/writer to)]
    (->> (csv/read-csv reader)
         (map #(rest (butlast %)))
         (csv/write-csv writer))))
What I have been struggling with, is how to combine them into a new function that reads in a CSV and writes out a CSV.gz. I'd like to do a similar version of this function that writes the CSV to a .nippy file as well

rolt14:03:30

if it's just csv to csv.gz:

(with-open [writer (-> to io/output-stream GZIPOutputStream. io/writer)]
  (io/copy (File. from) writer)
if you're changing format start from your copy-csv function but change to writer to the one above

Nick14:03:47

Thanks. I modified the function to be as follows:

(defn csv->gz [from to]
  (with-open [reader (io/reader from)
              writer (-> to io/output-stream GZIPOutputStream. io/writer)]
    (->> (csv/read-csv reader)
         (map #(rest (butlast %)))
         (csv/write-csv writer))))
It works šŸ™‚

ghadi16:03:59

this should be three functions

ghadi16:03:57

read / transform / write. let a top level fn open/close the files

Nick18:03:35

@U050ECB92 Thanks. Isn't that what is done inside the ->> macro? Would this be more performant as separate function calls?

ghadi18:03:00

it's not about performance, it's just about sticking things together vs. keeping them apart

ghadi18:03:16

your transform kernel is buried #(rest (butlast %))

ghadi18:03:36

it's easier to put things together later, than take them apart later

stantheman20:03:24

Ok after a few days away from Clojure I found it quite tricky to do this. In the guide to spec there is a card game with the deal function stubbed. So, I thought good practice to write it, surprisingly awkward probably as old procedural ways of thinking have snuck up on me! Anyhow any constructive criticism is welcome particularly anything unidiomatic. Thanks...

hiredman20:03:29

something like (->> (range hands) cycle (map vector shuffled-deck) (group-by second) (map (fn [[hand cards]] [hand (map first (take hand-size cards))]))

phronmophobic21:03:17

some thoughts: ā€¢ I would probably make :game/card a map instead of a tuple. It sort of doesn't matter in the small, but if you're looking at a tuple in a large nested structure vs a tuple, then it's helpful. It's also makes idiomatic code more readable (ie. (:rank card) vs (nth card 0)). Finally, there might be reasons to add more properties (eg. :face-up?, :tapped?, :price, etc). ā€¢ potentially narrow :game/score to (s/or zero? pos-int?) ā€¢ Having built card games before, it's often convenient to use just numbers for rank in the model and only use names like jack, queen, king, ace for display. The main idea is that you often want to be able to compare ranks. It's also not a big deal to have utilities that compare ranks. ā€¢ it would probably be useful to have specs for rank and suit. ā€¢ what happens when you deal to a number of players that doesn't evenly divide the deck? ā€¢ I don't think you need [& hands] deck-part. You should be able to just pass deck-part. same for plays

hiredman21:03:30

then the deck after the deal is (drop (* hands hand-size) shuffled-deck)

hiredman21:03:14

(let [hands 5
      shuffled-deck (shuffle (range 52))
      hand-size 7]
  (->> (range hands)
       cycle
       (map vector shuffled-deck)
       (group-by second)
       (map (fn [[hand cards]] [hand (map first (take hand-size cards))]))
       (into {})))

hiredman21:03:24

{0 (25 30 45 16 43 15 5), 1 (49 10 28 0 48 11 35), 2 (18 21 26 24 12 14 8), 3 (2 7 36 51 20 29 37), 4 (42 33 17 4 32 3 9)}

stantheman21:03:42

Grand thanks phronmophobic hiredman . Yes I wondered about dropping the deck but was having so much 'fun' getting the hands to the players that was postponed šŸ˜‰ As you show threading may untangle a lot of logic? Ill try deck-part and plays without de-structure (a left-over really from de-structuring with more aliases... Had to open the wine from cellar now so should leave for clearer analysis!

mathpunk22:03:58

I have functions that maps from temporal intervals to names, like so:

[#object[org.threeten.extra.Interval0x5039c5d9 "2023-02-28T21:32:57.791Z/2023-02-28T21:39:05.368Z"] "bulk-action-permissions.feature"]
Thereā€™s only like, 8 or 10 elements in the mapping. Iā€™ve got a log file of ~10,000 lines, mostly starting with timestamps. What I want to do is split up the log by what name is related to the time interval (or associate the line with a name like ā€œnoneā€).

mathpunk22:03:09

So itā€™s funny because itā€™s almost like a map lookup

mathpunk22:03:33

But the timestamp isnā€™t the same as the interval, itā€™s possibly time/contains?ā€™d in the interval

mathpunk22:03:22

My current approach is,

mathpunk22:03:25

staring into space

mathpunk22:03:10

I keep starting to think about cond and then I go, no, that would be if you had a specific list of intervals and you were testing against them

mathpunk22:03:22

And I do, but itā€™s the result of a function, not just something Iā€™d type in

mathpunk22:03:56

at which point I think, ā€œHey write a macro that takes the keys of your map and conds through themā€

mathpunk22:03:13

but lucky for me any time I think ā€œmacroā€ I also think,

mathpunk22:03:04

so then I back away. But I know Iā€™ve seen weird variants of map, like mapv and I start to wonder, is one of those for this kind of case

mathpunk22:03:53

long ramble short, Iā€™m gonna look at some of the more advanced map-like or cond-like functions/macros in clojure.core and see if any of them are for this kind of problem

mathpunk22:03:07

other ideas v. welcome

mathpunk22:03:25

Maybe I just need to compose smaller and it will become clear.

mathpunk22:03:45

Oh I bet I can just filter through this mapping

mathpunk22:03:53

and then use that to slap the label onto my line

hiredman22:03:41

it's a sort of tree building problem, take the entire interval of the first and last timestamp on the log entries, and if multiple time points in your map are within that interval, split it into two intervals and recurse on each until you have have a bunch of intervals in you log that each cover only a single point in your map of labels

1
skylize01:03:04

Assuming the intervals are unique, isn't that just group-by?

(group-by interval->name log)

mathpunk01:03:08

nnnnno, I don't think so... i have a map from intervals to names that I'm trying to extend to instants to names

mathpunk01:03:03

luckily, the intervals are non-intersecting, so hiredman's solution is more general than I need

mathpunk01:03:19

of course now I'm rereading his and decreasingly confident of ^

mathpunk01:03:05

i think i should break this into instant->interval and interval->name

mathpunk01:03:07

since there's just a few intervals in my set

skylize01:03:50

(group-by (comp #(% intervals->names) inst->interval) log)

skylize01:03:58

Or if you want inst->interval in a map too

(group-by (comp #(% intervals->names) #(% insts->intervals)) log)

Mark Wardle05:03:19

Hi. So you have a log file which sometimes contains an interval and sometimes contains a timestamp. You have a function that can take the former and give it a name. You donā€™t know how to handle the latter? Part of the solution is breaking apart the steps here. Itā€™s not about looking for variants of map (mapv vs map). The first q is can you derive the label you need from a single item or do you need to analyse across the log file. If latter you might need something more complex. If former, and Iā€™m assuming that, then you can handle each row independently - to begin with. So donā€™t you need a function that takes a vector and calls your other function if it is an interval and returns ā€˜noneā€™ if not? Once you have that, you can just use group-by as others have suggested above. If this isnā€™t right for you, a minimal example of the data would be helpful - input and expected output.

mathpunk16:03:51

Yes, the solution I came up with was all about breaking things apart. Iā€™m not 100% sure itā€™s right, but hereā€™s what Iā€™ve got now:

(require '[java-time.api :as time])

(defn testing-interval [feature]
  (time/interval (feature-test-starts feature) (feature-test-ends feature)))

(defn inst->interval [inst]
  (let [testing-intervals (map testing-interval feature-data)
        matches (filter (fn [interval] (time/contains? interval inst)) testing-intervals)]
    (if (empty? matches)
      nil
      (first matches))))

(defn active-feature
  [line]
  (let [testing-intervals (map testing-interval feature-data)
        intervals->features (zipmap testing-intervals feature-data)]
    (when-let [inst (timestamp line)]
      (let [interval (inst->interval inst)]
        (get intervals->features interval)))))
I was thrown by some NPEs I was getting when I tried counting some intermediate steps, then I realized it was because I was realizing more of the sequence, and not every line corresponds with an interval, nor does every line have a timestamp.

šŸ‘ 2