Fork me on GitHub
#beginners
<
2019-09-02
>
jumar06:09:49

@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 (https://purelyfunctional.tv/lesson/card-functional-abstraction/) mentions a problem when you originally chose incorrect data representation and you hardcoded it everywhere in the code base - In https://www.infoq.com/articles/in-depth-look-clojure-collections/ (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?

jumar06:09:49

@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 (https://purelyfunctional.tv/lesson/card-functional-abstraction/) mentions a problem when you originally chose incorrect data representation and you hardcoded it everywhere in the code base - In https://www.infoq.com/articles/in-depth-look-clojure-collections/ (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?

jumar06:09:47

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

alexmiller12:09:23

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

alexmiller12: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

alexmiller12:09:22

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

alexmiller12:09:47

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

alexmiller12: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

jumar12:09:29

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.

seancorfield16:09:08

@ 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.

seancorfield16:09:25

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.

seancorfield16:09:51

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 ๐Ÿ™‚ )

borkdude07:09:53

@gisf You may be interested in https://re-find.it/ if you want to find a function for an example to an example: e.g. 1 => "1" https://re-find.it/?args=1&amp;ret=%221%22

htarashion08:09:36

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

hobosarefriends08:09:01

have you tried with: https://github.com/r0man/cljs-http If you're more used to ajax maybe: https://github.com/JulianBirch/cljs-ajax I'm not great at cljs, so if someone recommends something take their opinion first.

hobosarefriends08:09:14

if it isn't what you're looking for you can also check in or post the question on: https://ask.clojure.org ๐Ÿ˜„

htarashion08:09:24

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

vnorilo_clojure10:09:41

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

htarashion11:09:30

can you give me an example?

lkowarschick11:09:59

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:

(foo
  [+ 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...)

lkowarschick11:09:03

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)
                    args)))]
    `[[email protected]#]))

lkowarschick11:09:36

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...

henrik11:09:39

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

henrik11:09:49

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

henrik11:09:59

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

henrik12:09:17

(where a is (def a 9))

lkowarschick12:09:01

ill try to reproduce it in the most simple way

lkowarschick12:09:14

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?

lkowarschick12:09:37

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

linkerfelix12: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 com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl.parse(org.xml.sax.InputSource,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 https://groups.google.com/forum/#!forum/clojure? Or is there something I could do about this.

alexmiller12: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)

linkerfelix12:09:54

So I just should wait and pray? ๐Ÿ˜‡

alexmiller12:09:49

To understand more, this faq entry is helpful https://clojure.org/guides/faq#illegal_access

alexmiller12: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 clojure.data/xml

alexmiller13:09:25

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

alexmiller13: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

alexmiller13:09:05

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

linkerfelix13:09:27

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

alexmiller13:09:26

Are you using the latest? I thought these issues were fixed in clojure.data.xml

alexmiller13:09:14

0.2.0-alpha6 is latest

linkerfelix13:09:34

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

alexmiller13:09:43

despite the name, latest is very stable

alexmiller13:09:52

(and prior is pretty old now)

linkerfelix14:09:43

Works fine with latest! Thanks for the hint on that library ๐Ÿ™‚

patrick.przystolik12:09:11

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

sova14:09:44

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

sova15:09:47

And I thought I had a problem with react keys but... i just fixed it ๐Ÿ˜„

sova15:09:53

amazing what fresh eyes can do

alberson17:09:06

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 ->>?

alberson17:09:49

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

alexmiller17:09:18

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

alexmiller17: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

alexmiller17: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

alberson17:09:57

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

alexmiller18: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.

alberson18:09:41

Oh, now I get it. Thanks for the explanation

jumar11:09:24

@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))
  @cnt1)
;; 66 (2 x 33 - chunked seqs?)
Am I misinterpreting you?

risinglight17:09:31

out of curiosity, any idea how the name โ€œeductionโ€ came to be?

alexmiller18:09:14

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

risinglight18:09:11

huh, TILโ€ฆ thanks!

vachichng21:09:26

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}]}]}]) 

vachichng21:09:45

to this:

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

vachichng21:09:06

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

schmee21:09:28

you can use assoc-in for this:

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

vachichng21:09:04

@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?

schmee21:09:24

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

vachichng22:09:32

many thanks! it worked!

vachichng22:09:20

(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] ()))) 

vachichng22:09:48

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

schmee22:09:16

where is the data coming from? is it clojure.data.xml?

seancorfield22:09:58

@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.

neo255122:09:01

@vachichng did you try to write a recursive function?

neo255122:09:20

Or use use clojure.zip?

seancorfield22:09:01

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?

vachichng22:09:37

@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

seancorfield22:09:16

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.

vachichng22:09:04

@neo2551 I will have a look at clojure.zip , thanks!

vachichng22:09:26

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

d.ian.b22:09:45

hey guys, I didn't understood this error

d.ian.b22: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')

d.ian.b22:09:27

I made this error using environ

d.ian.b22:09:44

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

d.ian.b22:09:48

and doing this

dpsutton22:09:09

(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?

d.ian.b22:09:25

I used here

d.ian.b22:09:31

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

d.ian.b22:09:34

thanks ๐Ÿ˜ƒ

johnjelinek23:09:45

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

(ns hello-cdk.hello-stack
  (:gen-class
   :extends software.amazon.awscdk.core.Stack
   :constructors {[software.amazon.awscdk.core.Construct String] [software.amazon.awscdk.core.Construct String nil]
                  [software.amazon.awscdk.core.Construct String software.amazon.awscdk.core.StackProps] [software.amazon.awscdk.core.Construct String software.amazon.awscdk.core.StackProps]}
   :init "init")
  (:import (software.amazon.awscdk.core Construct Stack StackProps)
           (software.amazon.awscdk.services.s3 Bucket BucketProps)))

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

seancorfield23:09:25

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

seancorfield23:09:07

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...

seancorfield23:09:17

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

johnjelinek23:09:05

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 software.amazon.awscdk.core.Stack :constructors {[software.amazon.awscdk.core.Construct String] [software.amazon.awscdk.core.Construct String nil], [software.amazon.awscdk.core.Construct String software.amazon.awscdk.core.StackProps] [software.amazon.awscdk.core.Construct String software.amazon.awscdk.core.StackProps]} :init init) (:import (software.amazon.awscdk.core Construct Stack StackProps) (software.amazon.awscdk.services.s3 Bucket BucketProps))) - failed: Extra input spec: :clojure.core.specs.alpha/ns-form

johnjelinek23:09:25

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

seancorfield23:09:34

Like I said "not the only problem" ๐Ÿ™‚

seancorfield23:09:54

I almost never use :gen-class and I wrestle with it every time I do... ๐Ÿ˜ž

johnjelinek23:09:04

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

seancorfield23:09:22

Yeah, exactly. I said it wouldn't help ๐Ÿ˜

johnjelinek23:09:00

: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?)

johnjelinek23:09:56

wat ... it got fixed when I remove nil

seancorfield23:09:07

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

johnjelinek23:09:12

this evaluates:

(ns hello-cdk.hello-stack
  (:gen-class
   :name hello-cdk.hello-stack
   :extends software.amazon.awscdk.core.Stack
   :constructors {[software.amazon.awscdk.core.Construct String] [software.amazon.awscdk.core.Construct String]
                  [software.amazon.awscdk.core.Construct String software.amazon.awscdk.core.StackProps] [software.amazon.awscdk.core.Construct String software.amazon.awscdk.core.StackProps]}
   :init init)
  (:import (software.amazon.awscdk.core Construct Stack StackProps)
           (software.amazon.awscdk.services.s3 Bucket BucketProps)))

seancorfield23:09:28

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

johnjelinek23:09:37

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

seancorfield23:09:47

You need the type there.

seancorfield23:09:59

software.amazon.awscdk.core.StackProps

johnjelinek23:09:25

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?

seancorfield23:09:41

You write the constructor.

johnjelinek23:09:38

(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

seancorfield23:09:01

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

seancorfield23:09:46

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

seancorfield23:09:37

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.

seancorfield23:09:49

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

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

seancorfield23:09:23

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

seancorfield23:09:31

Does that help @johnjelinek?