Fork me on GitHub
#beginners
<
2020-12-11
>
Rowan Barnard03:12:18

Hi everyone I was reading about final in a Java book and how java.lang.String is a final class so it can't be extended and the book was explaining about how it would be dangerous and chaotic to have alternative implementations of String be accepted polymorphically by methods but Clojure's polymorphism allows you to extend even final classes, if so then wouldn't that be dangerous also or is there something I'm not understanding that makes this OK to do?

seancorfield04:12:02

@flyingpython In Java, polymorphism is intrinsically tied to inheritance. In Clojure, that is not the case.

seancorfield04:12:08

Clojure's polymorphism is, essentially, a la carte. Protocols allow you to associate type-based dispatch with additional functionality.

andy.fingerhut04:12:14

Would it be fair to say that Clojure's protocols would allow you to create dangerous and chaotic protocol implementations, if you wished, perhaps in a similar way that the Java book claims that extending java.lang.String might be? At least, it doesn't seem like Clojure somehow prevent that from happening.

andy.fingerhut04:12:43

I agree that some kinds of danger and chaos are avoided by not having implementation inheritance -- just not all of them. There are so many kinds of danger and chaos, after all 🙂

seancorfield04:12:06

One person's chaos is another person's ordered functionality... 🙂

seancorfield04:12:47

This is why you should only extend protocols you own or types you own. But, sure, you can write dangerous, chaotic code with Clojure (just like any language, even Java, despite final 🙂 )

Rowan Barnard05:12:21

Ah OK thanks for the explanation guys, so I guess it is mostly avoided by convention of not doing unwise things, it seems super cool that you can do that in Clojure though 🙂

seancorfield05:12:58

And it's probably worth noting that next.jdbc extends Sourcable to String so that you can polymorphically call get-datasource on a JDBC URL as a string, just like you can call it on an associative data structure.

Rowan Barnard05:12:42

Wow that's super cool!

seancorfield04:12:45

For example, in next.jdbc, it has the concepts of "sourceable" -- something you can make a datasource from -- and "connectable" -- something you can make a connection from. That allows you to treat a number of things in a consistent, polymorphic, manner and work in a natural way with things that perhaps weren't designed with the affordances you need.

Rowan Barnard05:12:16

Thanks for the explanations, yes the book I am working through on Clojure said working with databases is a common use case for Clojure's polymorphism

roelof06:12:42

I know I ask this yesterday but the solution then do not seem to solve it

roelof06:12:47

clj::ground_up.chapter5=> (ns ground-up.chapter5)
nil
clj::ground-up.chapter5=> 
; Syntax error compiling at (chapter5.clj:38:1).
; Unable to resolve symbol: defn in this context

roelof06:12:58

code :

(defn palindrome?
  "Checks if a word is a palingdrome"
  [word]
  (= (seq word) (reverse word)))

Daniel Östling07:12:58

In a cider repl, how do I print a “complex” map in a def-friendly way? I would like to be able to cut & paste from repl into a def in testing code, but I run into issues like lists being (1 2 3) in the output instead of ’(1 2 3) or (list 1 2 3), for example.

jumar07:12:42

Every now and then I just quote such "lists" manually. If this is for debugging/inspection you can use cider inspector or debugger and press d when you're focused on the right object - it will prompt you for a var name.

dpsutton07:12:50

(walk/postwalk (fn [x] (if (and (seq? x) (not (vector? x)))
                         `(~'list [email protected])
                         x))
               {:a (range 3)
                :b {:c (range 3)}
                :d (map inc (range 3))
                :e [(map inc (range 3))]})
bit hacky but can work for ya

dpsutton07:12:05

you'll need to tweak the predicate a bit i'm sure but seems promising

Daniel Östling07:12:47

@U06BE1L6T, I’ve been playing around in the repl to build a test, and wanted to transfer an expected result to a test case.

Ben Sless07:12:11

(def foo (quote ,,,,))?

Daniel Östling07:12:23

Do I have to do prn and use reader?

roelof07:12:59

Can someone explain this :

(defn palindrome?
  "Checks if a word is a palingdrome"
  [word]
  (= (seq word) (reverse word)))

(defn countPalingdromes
  "count the number of palingdromes between 0 and 9999"
  []
  (->> (range 0 9999) (filter palindrome?) (count)))
error message :
; Execution error (IllegalArgumentException) at ground-up.chapter5/palindrome? (form-init12029216532296771069.clj:41).
; Don't know how to create ISeq from: java.lang.Long

phronmophobic07:12:18

palindrome expects a sequence and it's being given a number. try: (->> (range 0 9999) (map str) (filter palindrome?) (count))

roelof07:12:08

still a lot to learn

scythx08:12:40

Anyone know how to request with content-type image/png in reitit? I need to create upload image API. Multipart also won't works, i keep getting ClassNotFoundException javax.servlet.http.HttpServletRequest

roelof08:12:09

how do I make it work that the message is printed

roelof08:12:14

(def logging-enabled :true)

(defmacro log [message]
  (if (= logging-enabled :true)
    '(prn message)
    nil))

(macroexpand `(log :hi))

Young-il Choo20:12:34

To define macros, you need to understand the difference between • quote (’ apostrophe) • syntax-quote (` back-tick) • unquote (~ tilde) The syntax quote needs to be used, when you need to create an expression using a variable’s value, not the variable itself, done by unquoting the variable. In your example you need to syntax quote the (prn …) and unquote message. Also, you need to quote nil, since that is a return expression.

roelof08:12:58

right now I see (prn message) as output

roelof08:12:23

before I forget this is the challenge I try to solve:

; Write a macro log which uses a var, logging-enabled, to determine whether or 
; not to print an expression to the console at compile time. If logging-enabled 
; is false, (log :hi) should macroexpand to nil. If logging-enabled is true, 
; (log :hi) should macroexpand to (prn :hi) .

roelof08:12:59

if I restart vs code I see this as soon as I do crtl - enter

roelof08:12:05

Syntax error compiling at (chapter5.clj:62:1).
; Unable to resolve symbol: defmacro in this context

roelof08:12:42

even if I do (ns ground_up.chapter5)

roelof09:12:34

wierd, if i do crtl-enter first on a defn and then on the defmacro it works, If I do tit direct on a defmacro I see the above error

pez16:12:02

With Calva you should always start with loading the file. Otherwise, things are in flux.

mzuppichin09:12:17

Hello Clojurians, I am trying to build subsequences from an original seq according to specific criteria (4Clojure p53, https://www.4clojure.com/problem/53#prob-title). I am trying to implement it via a recursive function, any suggestions for a higher level approach? Thanks in advance.

practicalli_john10:12:20

@mzuppichin a code walk through of how I put together first a very low abstraction loop recur and then used partition for a higher level of abstraction https://github.com/practicalli/four-clojure/blob/master/src/four_clojure/053_longest_increasing_sub_seq.clj

mzuppichin10:12:54

@jr0cket Thanks sincerely, I have tons of material to study now!

mzuppichin10:12:23

Thanks for the quick and exhaustive response!

practicalli_john10:12:17

If you can find any other solutions, do let me know 🙂

practicalli_john10:12:05

The namespace in the file should use a dash instead of an underscore

(ns ground-up.chapter5
  (:import [java.time LocalTime]))
However, the file name should remain as an underscore, as the Java virtual machine that this Clojure code runs on does not support dashes in file names. I appreciate this is awkward at first.

henryw37411:12:15

I might model the schedule as a sequence of activity name with time entries

roelof11:12:05

chancing the name into (ns- ground-up.chapter5) does not solve the problem

roelof11:12:47

I still get this error message :

; Unable to resolve symbol: defmacro in this context

practicalli_john11:12:11

I am unaware of the problem, I havent looked into the history. However the ns definition I suggest is the preferred syntax.

roelof11:12:12

@U051B9FU1 how do you mean that ?

roelof11:12:16

oke, if I do crtl + enter on the new name it works

practicalli_john11:12:08

I am unclear why macros are used. They are a small part of writing clojure applications and require more of a learning curve if you are just beginning. Not something I can help with, sorry.

roelof11:12:52

NP. it seems your suggestion worked on chancing the name

👍 1
henryw37411:12:11

(def my-schedule 
[{:activity "brush teeth" :time "7"}
{:activity "gel hair" :time "7.15"}])

henryw37411:12:06

for example. then you can have the same fn work on different schedules. and you can validate them to see if they make any sense

dharrigan11:12:05

There is also the #code-reviews channel that may elicit more feedback 🙂

roelof11:12:13

Thanks, I ask there

roelof11:12:09

@U051B9FU1 I think that I understood what you mean but I also think that is more then I can chew now. Writing clojure now for 3 - 4 days

Marcus11:12:23

Do any of you know if the spit function reads entire file content when using the append true parameter?

roelof12:12:26

Confusing this :

user=> (def x (future (prn "hi") (+ 1 2)))
"hi"
#'user/x
user=> (deref x)
3

roelof12:12:48

why does hi printed direct and the adding later

roelof12:12:59

why not both later ?

Jim Newton12:12:01

is there a function build-in which reverses the args of a binary function. i.e. which maps f to (fn [a b] (f b a)) ?

Jim Newton12:12:44

of course I can write this but it is one of those functions like comp which is useful when doing functional manipulation

Jim Newton12:12:50

I need this most often when using condp to reverse the args of the predicate

Jim Newton14:12:35

I need to find the most frequent element of a sequence, and how many times it occurs. The way I'm doing it seems awkward.

(defn most-frequent [items]
  (reduce (fn [[td c] [td' c']]
            (if (< c' c)
              [td c] 
              [td' c']))
          [:empty-set 0]
          (frequencies items)))
So (most-frequent '(a b a b c a b a b a a a d)) returns [a 7]

Stuart14:12:28

(->> (frequencies '(a b a b c a b a b a a a d))
     (sort-by val >)
     (first))
?

Jim Newton14:12:17

ok, sorting a map using sort-by val is something i wouldn't have guessed. The code is concise, but I don't really need to sort, I just need to find the maximum element

Stuart14:12:46

do you need the key or the val?

Jim Newton14:12:16

perhaps something like max-key would be cleverer?

Jim Newton14:12:34

once I have the key, I can get the value, right?

Stuart14:12:08

if you just want the value

(apply max (vals (frequencies '(a b a b c a b a b a a a d))))

Stuart14:12:43

oh nice! I wasn't aware of max-key 🙂

Jim Newton14:12:49

the doc for max-key suggests this recipe:

; find the key that has the highest value in a map
user=> (key (apply max-key val {:a 3 :b 7 :c 9}))
:c

Jim Newton14:12:49

so perhaps something like the following will give me the pair

(apply max-key val {:a 3 :b 7 :c 9})

Jim Newton14:12:24

max-key is basically what my call to reduce is doing, more or less.

Stuart14:12:38

yeah, seems like it.

Jim Newton14:12:52

cool. I'm glad I asked. thanks for the leads.

Jim Newton14:12:38

when using something like max, one must consider the cases of empty input.

Jim Newton14:12:33

for example my function dies when given an empty list.

nate14:12:10

(some->> (frequencies [])
         seq
         (apply max-key val))

👍 2
nate14:12:17

Some threader can help...

Michaël Salihi19:12:59

I just posted this solution with max-key a few days ago on Twitter https://twitter.com/PrestanceDesign/status/1336726602777038849 max-key is great!

borkdude14:12:27

(->> (frequencies [1 2 3 1 3]) (sort-by val >))
([1 2] [3 2] [2 1])

dgb2314:12:31

When I first checked out the frequencies source, I learned like 3 new things at once. And the function is only a couple of lines long

dgb2314:12:41

It’s very motivating to read the clojure.core sources for these utility functions.

1
Jim Newton16:12:12

I think the documentation for condp is wrong. can someone help me read it and figure out if I'm confused. The documentation says that the expression (pred test-expr expr) is evaluated repeatedly, however it appears to me that expr is only evaluated once, into a tmp-var and the expression that is evaluated repeatedly is (pred-test-expr tmp-var)

Jim Newton16:12:28

This is of course the desired behavior, but not the documented behavior.

alexmiller16:12:33

I think that's what that doc is trying to convey

Jim Newton16:12:36

I agree that that is what's intended, and it is what the user would assume from NOT reading the doc. But when I read it carefully, I had to perform an experiment to verify.

alexmiller16:12:29

if you're using a side effecting expr where you would see a different result between those two interpretations, then I would suggest maybe that's a bad idea

Jim Newton16:12:40

I'm writing a macro which expands to several invocations of condp. I needed to know whether my macro needs to capture the value of the expression in a let or whether condp already does that for me.

Jim Newton16:12:30

as to whether it is a good idea to have side effects in the given expression, I understand your concern. But if I'm writing a macro where the user passes the expression to me, I don't have the privilege of requiring that there be no side effects.

alexmiller16:12:46

sure you do - just say that's not allowed in the docs :)

Jim Newton16:12:46

Well I prefer that if the user gives me an expression with side effects, then those side effects are only effectuated a maximum of once. That is much friendlier for debugging.

Jim Newton16:12:30

And is in fact what condp does, despite a biblical fundamentalist reading of the holy documentation text 🙂 grins

Andrew Doolittle16:12:46

Hello all, I'm trying to create a CSV using (https://clojure.github.io/data.csv/) , however I don't want to write to disk and would like the output to be a byte array. Because I'm unfamiliar with writer, I'm not sure how to do this. (with-open [writer (io/writer file-path)] (clojure.data.csv/write-csv data file-path)) How can I change this to return a byte array and not write to disk? Thanks

dpsutton16:12:06

(let [bytes (java.io.ByteArrayOutputStream.)]
  (with-open [writer (io/writer bytes)]
    (csv/write-csv writer [["a" "b" "c"] [1 2 3] [4 5 6]]))
  (str bytes))

🙏 1
Andrew Doolittle17:12:44

Thanks. I changed the last part and got what I needed.

(defn data->byte-array [data]
  (let [bytes (java.io.ByteArrayOutputStream.)]
    (with-open [writer (io/writer data)]
      (csv/write-csv writer data))
    (.toByteArray bytes)))

(data->byte-array data) ;;  [34, 91, 34, 34, 48 ...]
(type (data->byte-array data)) ;; [B
Thanks again!

parens 1
lread16:12:07

I am working on rewrite-cljc docs and want to check my terminology usage. When I first came to Clojure I really had no concept “form” or “sexpr”. Maybe I still need help. Can these terms be used interchangeably? Is the difference https://clojure.org/guides/learn/syntax#_structure_vs_semantics?

Jim Newton17:12:34

I know that a string in quotes is an expression, but I'm not sure whether a non-parenthesized piece of text is a form????

Jim Newton17:12:02

strings and numbers and symbols are s-expressions.

Jim Newton17:12:05

are they forms?

delaguardo17:12:11

s-expression is either an atom (including strings and symbols) or an expression (x y …) where x and y are s-expressions so literals like [1 2 3] or {:x 1} (vectors and maps) are not s-expressions.

delaguardo17:12:00

s-expression is a subset of form in clojure terminology imho

lread17:12:53

interesting

lread18:12:02

Thanks @U010VP3UY9X and @U04V4KLKC, I guess I’ve tested the waters for strong opinions on this. I think I’ll just go ahead and describe the terms rewrite-clj already uses with the caveat they might not be absolutely technically correct.

lread20:12:04

Found this definition which seems to match rewrite-clj usage: https://www.braveclojure.com/do-things/#Forms

roelof17:12:31

Confusing this :

user=> (def x (future (prn "hi") (+ 1 2)))
"hi"
#'user/x
user=> (deref x)
3
why does hi printed direct and  the adding later why not both later ?

alexmiller17:12:05

future runs the expression in a different thread

alexmiller17:12:17

concurrent to the main repl thread

alexmiller17:12:31

(although they both share the same stdout which can be confusing)

dpsutton17:12:48

from (doc future) > Takes a body of expressions and yields a future object that will > invoke the body in another thread, and will cache the result and > return it on all subsequent calls to deref/@.

alexmiller17:12:58

so "hi" is being printed in thread 2, then the deref returns the result of the expression and prints it in the main thread

roelof17:12:01

oke, so printed cannot be deferred. If I would do there another calculation then both were deffered ?

hiredman17:12:18

it sounds like you are expecting the behavior of delay

hiredman17:12:18

a delayed expression is only run once you deref or force the delay (and happens on the same thread)

roelof17:12:04

I expect nothing but try to understand what happens. It thought both are deffered but I see the output of `(prn "hi") direct and I try to understand why that one is direct executed and the calculation is deffered

hiredman17:12:48

there is no real ordering of output from another thread (the future) and the output from the repl process (prompting you for input)

hiredman17:12:16

they both just print whenever they want, they are independent threads

hiredman17:12:52

so basically it is a race condition, how the output of the repl is interleaved with the output of the future

andy.fingerhut17:12:00

future will evaluate everything in its body as soon as possible, in a separate thread, subject to operating system, thread scheduling, etc. constraints.

hiredman17:12:45

so "hi" may print out first, or after #'user/x, or after you deref

alexmiller17:12:48

^^ future is not about future evaluation, it's about getting the result in the future

alexmiller17:12:34

evaluation is immediate and concurrent

alexmiller17:12:22

Clojure Applied has a great chapter on all this btw, also the author is quite strong and handsome

😂 2
👏 1
noisesmith17:12:27

@roelof perhaps one source of your confusion is that some output in the repl is the implicit print of values as you ask for them to be calcuated, and other output is arbitrary printing behavior?

roelof17:12:51

oke, so using a future is not a good thing because you never know when the result is returned. And there im confused

noisesmith17:12:21

the result is returned when you ask for it

noisesmith17:12:28

any random prints are done as they are executed

noisesmith17:12:22

this is why I was pointing out the distinction between things implicitly printed (because your REPL form returned them), and things explicitly printed (because you wrote code that prints)

hiredman17:12:08

the result of an expression, and output printed by an expression are not the same thing

dpsutton17:12:27

> oke, so using a future  is not a good thing a future is a thing that could be exactly what you need or very inappropriate. Can you explain what you want to accomplish and the advice can be more tailored to that?

roelof17:12:48

oke, lets say we have this : (def x (future (* 3 4 ) (+ 1 2))) I see a message #'ground-up.chapter5/x which is the 3

roelof17:12:13

but where is the outcome of ( *3 4) ?

dpsutton17:12:28

(do (+ 1 2) (+ 10 20)) will return 30. the (+ 1 2) is computed and ignored as its unused anywhere and not returned

noisesmith17:12:10

this isn't just a property of future (which contains an implicit do), it also applies to other places that contain a do block, like let, fn, defmacro, doseq, loop...

noisesmith17:12:36

all forms in the do block but the one you return are only useful as side effects

roelof17:12:38

oke, I think I understand it but still I think a wierd example in the book to explain futures

noisesmith17:12:06

it's showing that the execution happens immediately (via the print side effect), and you can get the value whenever you are ready for it

dpsutton17:12:34

it seems like it really demonstrated what's going on. the println happened immediately and the value comes when derefed. Pretty good probing of how future runs immediately and caches its value for dereferencing

roelof17:12:13

anyone more who want ot give feedback on my code.See question on the code-review chapter

Stuart18:12:43

Hi, I have this deps.edn

{:paths ["src" "resources"]
 :deps {org.clojure/clojure {:mvn/version "1.10.1"}
        aysylu/loom {:mvn/version "1.0.2"}
        org.clojure/math.combinatorics {:mvn/version "0.1.6"}}
 :aliases
 {:remote-repl {:extra-deps {vlaaad/remote-repl {:mvn/version "1.1"}}}
  :reveal {:extra-deps {vlaaad/reveal {:mvn/version "1.0.130"}}}
  :test {:extra-paths ["test"]
         :extra-deps {org.clojure/test.check {:mvn/version "1.0.0"}}}
  :runner
  {:extra-deps {com.cognitect/test-runner
                {:git/url ""
                 :sha "b6b3193fcc42659d7e46ecd1884a228993441182"}}
   :main-opts ["-m" "cognitect.test-runner"
               "-d" "test"]}
  :uberjar {:extra-deps {seancorfield/depstar {:mvn/version "1.1.128"}}
            :main-opts ["-m" "hf.depstar.uberjar" "aoc.jar"
                        "-C" "-m" "stuartstein777.aoc"]}}}
And this .clj file:
(ns stuartstein777.2020.day10
  (:require [clojure.string :as str]
            [clojure.math.combinatorics :as combo]))
WHen I try to run anything in the repl in this namespace I just get the error:
Execution error (FileNotFoundException) at stuartstein777.2020.day10/eval1477$loading (day10.clj:1).
Could not locate clojure/math/combinatorics__init.class, clojure/math/combinatorics.clj or clojure/math/combinatorics.cljc on classpath.
I'm using INtelliJ, after recent update I get warnings about old project types and it asks to convert them. Now stuff that worked pre-update is broken. Any ideas?

Stuart18:12:51

Cant run REVL either, and this also worked until yesterday. Now it says cant run REVL, no project file found.

Stuart18:12:26

Maybe I just need to recreate the project from scratch with clj-new ?

noisesmith18:12:06

it sounds like something in IntelliJ that needs fixing, not clj

Stuart18:12:23

yeah, i just tried another project that 100% worked, and woudl run REVL too. Now it doesn't work either.

Stuart18:12:35

Man, I have no other editor setup to do clojure

Stuart18:12:08

Guess I can use my other machine that doesn't have latest IntelliJ

noisesmith18:12:48

running clj in a terminal, then using require to load your namespace should still work (or at least get further than not seeing a dep that's in your config)

manutter5118:12:00

You might want to post something in the #cursive channel

💯 1
Singh21:12:03

Hi Everyone, I am a Javascript developer, I recently gotten introduced to Clojure world by a React ClojureScript application. Are there any good resources you would recommend that would will help me with this transition?

borkdude21:12:02

@singhamritpal49 If you're using Reagent, this might be a nice one: https://www.learnreagent.com/

william21:12:01

Hmm.. the free version of that course never loads for me.. I just get loads of errors in the js console

william21:12:17

In chromium it works fine, the errors were just for firefox

borkdude21:12:11

Maybe pass that as feedback to the author. It's kind of ironic that a webdev course doesn't work in all browsers.

william21:12:07

yes, that's the same thing I thought 😂 I already wrote about this problem in the chat that popped up

Michaël Salihi22:12:44

Hi! This is a good challenge for Clojure: https://twitter.com/wesbos/status/1337453660482187268 I'm sure there are a clean and simple way to solved this. What functions do you think are appropriate in order to achieve the results?

Michaël Salihi22:12:10

I have looked into the question and I am thinking of using merge-with, reduce, etc.

dpsutton22:12:54

(let [col [["name" "id" "height"] ["Bob" "2" "50"] ["John" "1" "45"]]]
  (map #(zipmap (map keyword (first col)) %) (rest col)))
({:name "Bob", :id "2", :height "50"}
 {:name "John", :id "1", :height "45"})

👍 1
Michaël Salihi07:12:38

Thx @U11BV7MTK for the clue! This is my first iteration, what do you think? There is a more idiomatic way to get the result? https://paiza.io/projects/9-Daq1Zrz1s-al0nqE0YUA?language=clojure

(ns challenge
  (:require [clojure.pprint :as pp]))

(def arr1 [["name", "id", "age", "weight", "Cool"]
           ["Susan", "3", "20", "120", true]
           ["John", "1", "21", "150", true]
           ["Bob", "2", "23", "90", false]
           ["Ben", "4", "20", "100", true]])

(def arr2 [["name", "id", "height"]
           ["Bob", "2", "50"]
           ["John", "1", "45"]
           ["Ben", "4", "43"]
           ["Susan", "3", "48"]])

(def arr3 [["name", "id", "parent"]
           ["Bob", "2", "yes"]
           ["John", "1", "yes"]])

(defn parse-array [col]
  (map #(zipmap (map keyword (first col)) %) (rest col)))

(defn merge-array [arr]
  (apply concat (map parse-array arr)))

(def result (map #(apply merge (val %)) (group-by :name (merge-array [arr1 arr2 arr3]))))

(pp/print-table [:id :name :age :weight :height :Cool :parent]
                result)

Vincent Cantin08:12:27

@UFBL6R4P3 Shouldn’t it be group-by :id instead of group-by :name ?

Vincent Cantin08:12:49

I suggest to avoid the shortcut for the anonymous function, so that you can name the entry param. It helps the code staying readable.

Vincent Cantin08:12:40

Another suggestion is to use ->>, it helps reading the transformations as they occur chronologically.

Vincent Cantin08:12:06

parse-array is fine

Vincent Cantin08:12:23

If you use my extended version of group-by , you can have something even shorter and easier to read:

(def result
  (->> [arr1 arr2 arr3]
       (map parse-array)
       (apply concat)
       (group-by :id identity merge)
       vals))

Vincent Cantin08:12:35

The source code of my group-by is:

(defn group-by
  "Same as clojure.core/group-by, but with some handy new arities which apply
   custom map & reduce operations to the elements grouped together under the same key."
  ([kf coll]
   ;(group-by kf identity conj [] coll)
   (cc/group-by kf coll))
  ([kf vf coll]
   (group-by kf vf conj [] coll))
  ([kf vf rf coll]
   (group-by kf vf rf (rf) coll))
  ([kf vf rf init coll]
   (->> coll
        (reduce (fn [ret x]
                  (let [k (kf x)
                        v (vf x)]
                    (assoc! ret k (rf (get ret k init) v))))
                (transient {}))
        persistent!)))

st3fan22:12:57

I’m doing well with “simple” recursion, like with loop/recur or without. But what I am struggling with is the case when I have to recur into a list of things … like when generating a tree or explore multiple paths

hiredman22:12:55

I have gotten into the habit of starting with something like an secd or cek machine whenever I need to process a tree, because those are abstract machines for evaluating programs, and programs are trees

hiredman22:12:48

the idea of storing continuations in a stack, processing a list of results and then doing something in the middle of a tree, etc are common features of tree processing and program evaluation

hiredman22:12:20

I can't claim it is good, I don't think the programs that come out of it are particularly readable

st3fan22:12:32

interesting i should look into that ..

hiredman22:12:49

but it seems to be a universal technique

hiredman23:12:44

the last thing I did that with is a pretty printer for graphql schemas and it is very ugly

st3fan23:12:34

i need to read about some basic algorithms about path exploring i think

hiredman23:12:12

another thing you might look at is zippers

st3fan23:12:13

advent of code related 🙂

hiredman23:12:43

clojure ships with clojure.zip, which doesn't get a lot of love

hiredman23:12:50

there is also clojure.walk

st3fan23:12:23

oh zipper may actually be what i want

hiredman23:12:32

related to zippers are lenses, which there are a few clojure libraries for, or something like spectre

st3fan23:12:41

i think i basically need something higher level that allows me to give it a function to answer ‘what are the children for this node’ repeatedly until i have no more data for that branch

st3fan23:12:50

looks like a zipper may be able to do this

phronmophobic23:12:42

if you need to edit nodes in a nested immutable data structure, zippers are awesome. for simply walking a nested data structure, tree-seq can be an easier alternative

st3fan23:12:32

i need to generate it first 🙂

st3fan23:12:46

and then count stuff in it