Fork me on GitHub

@seancorfield @hiredman @alexmiller I've found the recent discussion about "data not functions" (`make-person` et al.) interesting - I'd call it a "constructors and selectors" debate 🙂 I've been grappling with this idea for a while and while it's true I haven't seen this very often to be used in Clojure code I'm not still sure if and when to use that approach. Especially since some people suggest to use this to build "abstraction barriers": - SICP chapter 2.1 suggests using constructors (like make-rat) and selectors (like numer, denom) to abstract away from the concrete representation of rational numbers (cons cells) - @ericnormand ( mentions a problem when you originally chose incorrect data representation and you hardcoded it everywhere in the code base - In (which I suspect comes from the first ed. of Joy of Clojure) they also show the build-move function which is basically (apply hashmap pieces) Is this perhaps more specific to other Lisps than Clojure which has richer data types and sequence abstraction?


Just to be clear; I only skimmed that SICP chapter, didn't read the whole thing.

Alex Miller (Clojure team)12:09:23

Imo, the idea that a function barrier is going to save you from making the wrong choice is a false one

Alex Miller (Clojure team)12:09:40

Changing the name of an attribute is going to leak everywhere, including through a barrier as it’s in the data and the data is first class and visible

Alex Miller (Clojure team)12:09:22

Thus using an extra layer of functions only makes your problem worse by creating another set of places to change

Alex Miller (Clojure team)12:09:47

If this is all in code you own, just lean on the attributes and change the attribute

Alex Miller (Clojure team)12:09:04

If it’s in code that interacts with others, take an additive approach - add new attributes that take over and let the old ones languish. Or if you need to change/remove, then create new aggregates


Perfect, thanks! How about the idea of "deferring decisions"? When they show an example how to postpone an implementation decision like computing GCD on rational numbers. You can compute GCD either in constructor (make-rat) or in selectors; in either case clients don't need to worry about that because they only use selectors to access rational number's components.


@U06BE1L6T That feels like a very artificial problem to me. And also one that is, IMO, inherently present in OOP where data hiding means you need to make those decisions.


If you have "just data", and "just computation" then it becomes transparent where it gets called -- and if you wanted to cache some (potentially expensive) computation, you build a wrapper function, and code paths that care about that caching can use that instead of the original function.


As for "originally chose incorrect data representation" -- yes, that can happen, even if you spend plenty of hammock time designing the data (but it should be fairly rare). Still, you can add a v2 representation and API and deprecate the old one. Such changes would be driven either by performance or functionality requirements so having a v1 API that is slow and/or limited and a v2 API that is faster and/or more powerful is a reasonable approach -- users will switch over pretty fast but keeping the old API around does (almost) no harm. And that v1 API could eventually be discarded once you know it has no clients ("break none" shall be the whole of the law, to appropriate and misquote 🙂 )


@gisf You may be interested in if you want to find a function for an example to an example: e.g. 1 => "1";ret=%221%22

❤️ 4
hamid tsh08:09:36

hi. how can i use http request between clojure as a server and clojurescript as a client?


have you tried with: If you're more used to ajax maybe: I'm not great at cljs, so if someone recommends something take their opinion first.


if it isn't what you're looking for you can also check in or post the question on: 😄

hamid tsh08:09:24

thanks for your answer. I tried the first link for client but it did't sync with clj-http library for clojure.

Vesa Norilo10:09:41

just using the javascript fetch api from cljs isn't too bad either

hamid tsh11:09:30

can you give me an example?


if it isn't what you're looking for you can also check in or post the question on: 😄


back to my macro-question from yesterday: it worked! but there is one big problem: i now can only use concrete values in my forms, and nothing that needs to be calculated (especially nothing that is dependent on locals stuff) is it even possible to avoid this problem? i tried things like eval-ing the rest of each form and quoting the first, but then i get cannot eval locals errors, which obviously make sense. any ideas? (my problem was the following: i need a macro that can get called like this:

  [+ 1 2]
  [str 123]
(vararg) and return a structure like this:
['(+ 1 2) '(str 123)]
without giving me the function-objects, but the actual symbols of the functions. i know realized that i might need a different return structure...)


wait i think I got it... altough it seems ugly:

(defmacro foo [& forms]
  (let [lists# (for [[func & args] forms]
                 (list 'apply 'list
                  (apply vector
                    (list 'quote func)


okay what could cause: Caused by: java.lang.RuntimeException: Unable to resolve symbol: apply in this context ? getting it somewhere related to my macro-definition from before, but the line-number doesn't help at all...


Could it be from elsewhere in the code? I get no such error:


(foo (str "hello" "world") (str "dog" a))


[(str "hello" "world") (str "dog" 9)]


(where a is (def a 9))


ill try to reproduce it in the most simple way


it could be related to me using that macro within a generated namespace or something, maybe i need to somehow first include core stuff in generated namespaces?


haha wow that seems to have been it.... i just added [clojure.core :refer :all] to my requrie thing in the namespace and now it seems to work

Felix Linker12:09:39

I updated my java recently and started to get this warning in one of my projects:

ARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by clojure.lang.InjectedInvoker/0x0000000801368040 (file:/C:/Users/felix/git/smvtrcviz/target/uberjar/smvtrcviz-0.1.0-SNAPSHOT-standalone.jar) to method,org.xml.sax.helpers.DefaultHandler)
WARNING: Please consider reporting this to the maintainers of clojure.lang.InjectedInvoker/0x0000000801368040
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
After digging around a bit I found a way to debug it and managed to pinpoint the source of the warning to clojure.xml$startparse_sax.invokeStatic(xml.clj:76). Is this an issue that is worth reporting? And if so: Is it right to do it here!forum/clojure? Or is there something I could do about this.

Alex Miller (Clojure team)12:09:08

There is a ticket for this, really it’s just a warning from use of reflection in clojure.xml (which really no one should use anyways)

👍 4
Felix Linker12:09:54

So I just should wait and pray? 😇

Alex Miller (Clojure team)12:09:34

In this particular case, the Clojure code in clojure.xml is intentionally reflective in ways that are difficult to fix. clojure.xml is very old and really superseded by libraries like

Alex Miller (Clojure team)13:09:25

This warning is just a warning - it can be safely ignored

Alex Miller (Clojure team)13:09:13

There are various flags you could pass to make this particular reflective access valid but I’m not sure it’s worth doing so

Alex Miller (Clojure team)13:09:05

I presume the usage is in smvtrcviz - that lib could be changed to use a better api

Felix Linker13:09:27

Well, just changed the xml parser to but that also has an illegal reflective access. Thanks for all the information, though!

Alex Miller (Clojure team)13:09:26

Are you using the latest? I thought these issues were fixed in

Felix Linker13:09:34

No, I was using stable. Will try latest as well.

Alex Miller (Clojure team)13:09:43

despite the name, latest is very stable

Alex Miller (Clojure team)13:09:52

(and prior is pretty old now)

Felix Linker14:09:43

Works fine with latest! Thanks for the hint on that library 🙂

Patrick P.12:09:11

this.tempWorkingPath = this.cfg.autoImport.tempWorkingDir.replace('$MANDANT$','M' + mandantNr);


Hey everyone, i'm using Rum to generate a japanese grammar quiz. It's cool, a fragment is hidden from you, it shuffles the fragment correct response with some faulty ones and renders them as buttons


And I thought I had a problem with react keys but... i just fixed it 😄


amazing what fresh eyes can do


Hi friends. I just came by the function eduction that takes some transducers, a list and apply them. My question is: when should I prefer eduction over ->>?


(->> (take 10) (filter odd?))
;; over
(eduction (filter odd?) (take 10))

Alex Miller (Clojure team)17:09:18

eduction is pretty specialized in that it is a delayed eager evaluation

Alex Miller (Clojure team)17:09:33

the most common use case is to set up a reduction over an external resource (file, db result, etc) and hand that to a caller, who can a) decide when to invoke it and b) know that since it's eager the resource use is complete

Alex Miller (Clojure team)17:09:53

vs lazy sequences which are also delayed (but throughout, not just at the beginning) but are harder to catch and handle the "end" for resource control


So opposite to ->>, eduction applies the transducers to the given list lazily?

Alex Miller (Clojure team)18:09:28

I'd say that's wrong on both counts. ->> creates a lazily computed sequence which will produce values as needed. eduction creates a pending computation. when you ask for the first value, it will compute the entire collection eagerly at that point.


Oh, now I get it. Thanks for the explanation


@alexmiller I got confused by your statement that eduction computes the whole collection eagerly when you ask for the first value. Trying something like this shows that it only consumes a "chunk" of a lazy seq:

(def cnt1 (atom 0))
(let [res (eduction (map #(do (swap! cnt1 inc) %)) (range 100))]
  (conj (rest res) (first res))
;; 66 (2 x 33 - chunked seqs?)
Am I misinterpreting you?


Anyone coming across this thread and wondering about similar things as @U06BE1L6T can find Alex's explanation in the thread at Bottom line: eduction itself is iterator-based, not inherently eager, but is often used with reduce, which is eager.


out of curiosity, any idea how the name “eduction” came to be?

Alex Miller (Clojure team)18:09:14

it's a real word. transduce means "to lead across". educe means "to draw out" or "to lead out"


huh, TIL… thanks!


hi guys, I'm having hard time to figure out how to go from this :

(def test-a [{:tag :categories}
             {:tag     :associations
              :content [{:tag     :categories
                         :content '()}
                        {:tag     :products
                         :content [{:tag :product}
                                   {:tag :product}
                                   {:tag :product}]}]}]) 


to this:

[{:tag :categories}
 {:tag     :associations
  :content [{:tag     :categories
             :content '()}
            {:tag     :products
             :content []}]}] 


which is set the : content inside of {:tag : products } to empty a vector


you can use assoc-in for this:

(assoc-in test-a [1 :content 1 :content] [])

👏 4


👍 4

@schmee i'm trying to use the same for a lazy seq , but it don't seem to work , what is the alternative for lazy seqs?


you can’t access lazy-seq elements by index (only using first/`rest`) but you can convert the lazy seq to a vector with vec


many thanks! it worked!


(let [remote-categories (remote-get categories)
      cat1              (remote-get (str categories "/12"))
      xml               (->xml cat1)]
  (as-> (:content xml) v
        (vec v)
        (update-in v [0 :content] vec)
        (update-in v [0 :content 16 :content] vec)
        (update-in v [0 :content 16 :content 1 :content] vec)
        (update-in v [0 :content 16 :content 1 :content 1] vec)
        (assoc-in v [0 :content 16 :content 1 :content] ()))) 


is there a nicer way? , I had to convert all the nested lazy seqs to vec in order to get it work


where is the data coming from? is it


@vachichng You don't need as-> there -- plain -> will work (and then omit v in all those expressions). as-> is intended to be used inside a -> pipeline rather than at the top-level.

👍 4
David Pham22:09:01

@vachichng did you try to write a recursive function?

David Pham22:09:20

Or use use


Overall tho', it looks like you might be better served by transforming this to some data structure that would be more amenable to processing -- can you explain in more detail what end result you're trying to achieve?


@seancorfield yeah, I'm consuming a webservice from prestashop, which serves xml data , what I'm trying to do , is get a xml from one instance and do a post to another instance ( migrate data ) with some fields changed


Ugh! That's an unpleasant task. In that case, I think the suggestion to use zippers is probably going to be your best approach so you can convert XML to data, modify it with zippers, and convert it back to XML.

👍 4
👏 4

@neo2551 I will have a look at , thanks!


so, zippers are the recomended way to do xml transformation?

Ian Fernandez22:09:45

hey guys, I didn't understood this error

Ian Fernandez22:09:47

class clojure.lang.Keyword cannot be cast to class java.lang.String (clojure.lang.Keyword is in unnamed module of loader 'app'; java.lang.String is in module java.base of loader 'bootstrap')

Ian Fernandez22:09:27

I made this error using environ

Ian Fernandez22:09:44

(->> environ/env (reduce-kv (fn [m k v] (assoc m (keyword "env" k) v)) {}))

Ian Fernandez22:09:48

and doing this


(keyword "bob" :bob) -> ClassCastException clojure.lang.Keyword cannot be cast to java.lang.String clojure.core/keyword (core.clj:595). You need to use strings not kewords. But what are you putting these with an env namespace?

Ian Fernandez22:09:31

I'm mixing another file into environ and calling this with another function that I use (let [:env/keys [...] ... )


do I need to run spec/explain on this to find out what the problem is?

(ns hello-cdk.hello-stack
   :constructors {[ String] [ String nil]
                  [ String] [ String]}
   :init "init")
  (:import ( Construct Stack StackProps)
           ( Bucket BucketProps)))

Syntax error macroexpanding clojure.core/ns at (REPL:1:1).
((:gen-class :extends :constructors {[ String] [ String nil], [ String] [ String]} :init "init") (:import ( Construct Stack StackProps) ( Bucket BucketProps))) - failed: Extra input spec: :clojure.core.specs.alpha/ns-form
class clojure.lang.Compiler$CompilerException


*e will print the entire exception and stack trace but I don't think it will help you...


I think the problem is that you omitted the :name option -- which is the only mandatory option for :gen-class if I'm reading the docs correctly...


Hmm, well, that's not the only problem... let me read the docs more closely...


added :name:

Syntax error macroexpanding clojure.core/ns at (src/hello_cdk//Users/johnjelinek/Documents/code/hello-cdk/src/hello_cdk/hello_stack.clj:1:1).
((:gen-class :name hello-cdk.hello-stack :extends :constructors {[ String] [ String nil], [ String] [ String]} :init init) (:import ( Construct Stack StackProps) ( Bucket BucketProps))) - failed: Extra input spec: :clojure.core.specs.alpha/ns-form


I tried wrapping this in an (s/explain .. didn't add any extra info


Like I said "not the only problem" 🙂


I almost never use :gen-class and I wrestle with it every time I do... 😞


#error {
 :cause "Call to clojure.core/ns did not conform to spec."


Yeah, exactly. I said it wouldn't help 😐


:data #:clojure.spec.alpha{:problems [{:path [], :reason "Extra input", :pred (clojure.spec.alpha/cat :docstring (clojure.spec.alpha/? clojure.core/string?) :attr-map (clojure.spec.alpha/? clojure.core/map?)


wat ... it got fixed when I remove nil


:init init not :init "init" as well I think?


this evaluates:

(ns hello-cdk.hello-stack
   :name hello-cdk.hello-stack
   :constructors {[ String] [ String]
                  [ String] [ String]}
   :init init)
  (:import ( Construct Stack StackProps)
           ( Bucket BucketProps)))


And, yeah, nil is not a class name. I was just about to point that out.


the thing is .... for the first constructor, it needs to pass nil to the third param of the superclass


You need the type there.



ya, that evals ... but, how do I tell it to pass null to that? does it happen automatically since there's only 2 params passed to the constructor?


You write the constructor.


(defn -init
  ([parent id] ,,,)
  ([parent id props] ,,,))
replacing the body with what to tell it to pass to super? I thought gen-class handled that part


The :constructors part tells Clojure what signatures to use for the generated Java code -- but you still write the code.


At least, that's my understanding... but, like I say, I don't use :gen-class very often.


The init function is weird. It returns a pair of values -- the first value is the list of values to pass to the super constructor, the second value is any state your class needs.


So based on your example, I'd expect something like:

(defn -init
  ([parent id] [[parent id nil] ,,,])
  ([parent id props] [[parent id props] ,,,]))


So in the two-arg case, you return a triple that is used to call the super constructor, passing nil as the third arg.


Does that help @johnjelinek?

👀 4