beginners

Getting started with Clojure/ClojureScript? Welcome! Also try: https://ask.clojure.org. Check out resources at https://gist.github.com/yogthos/be323be0361c589570a6da4ccc85f58f.
Andy Nortrup 2021-01-16T00:14:56.278300Z

(remove empty? (for [issue ((run-jql-query
                             "project = \"Connect\"  and issueLinkType in (\"relates to\") and resolution is empty"
                             :expand "issueLinks"
                             :fields "issuelinks"
                             :max-results 10)
                            :issues)]

                 (let [links (filter #(not= % (first (string/split (issue :key) #"-")))
                             (dedupe
                              (for [outboundLink (remove nil? (for [link (-> issue :fields :issuelinks)]
                                                                (get-in link [:outwardIssue :key])))]
                                (first (string/split outboundLink #"-")))))]
                       (if (not-empty links)
                         (hash-map (keyword (first (string/split (issue :key) #"-")))
                                   (to-array (for [link links]
                                     (keyword link))))))))
I’m a little ashamed of how messy that probably is. But such is life as a beginner. It starts as a query to the JIRA API, grabbing some pieces of the response, and trying to build a map of projects to the other projects they link out to. Goal is to build a graphviz graph from that.

2021-01-16T00:17:13.278500Z

what does the rest of the stacktrace say?

2021-01-16T00:17:28.278700Z

oh

2021-01-16T00:17:30.278900Z

actually

2021-01-16T00:17:54.279100Z

you are making primitve java arrays with to-array

2021-01-16T00:18:11.279300Z

(the [Ljava.lang.Object; in the error message)

2021-01-16T00:19:53.279500Z

and that is the error message you get when you try to use clojure.set/union with primitive arrays

2021-01-16T00:21:18.279700Z

stuff in clojure.set is generally intended to work on clojure's native set datatype, but it doesn't really do any input type checking, so sometimes people use it with collections that are not sets, and that sometimes works

2021-01-16T00:21:34.279900Z

but primitive java arrays are not collections or sets

Andy Nortrup 2021-01-16T00:27:01.280100Z

aahh

Andy Nortrup 2021-01-16T00:27:45.280300Z

That fixed it.

Andy Nortrup 2021-01-16T00:27:57.280500Z

Than kyou so much @hiredman and @robert.mitchell36

seancorfield 2021-01-16T00:34:49.280700Z

@borkdude Sorry, been head down in a code spike all afternoon (after getting my brain swabbed for COVID earlier today which was unpleasant...)... So, er, what does Leiningen actually do here?

seancorfield 2021-01-16T00:36:30.280900Z

Does lein just binary-copy those files into the uberjar?

2021-01-16T04:16:55.281100Z

You don't want to rely on a dependency manager because it can't handle reproducibility and rollbacks as well

2021-01-16T04:18:52.281300Z

So like if you just installed clj on the server, than ran your code, its possible the dependencies it pulls down will be different on one node from another

2021-01-16T04:19:49.281500Z

Its possible to do it though, if you use something like Artifactory

2021-01-16T04:20:29.281700Z

Where you can make sure your own repo of artifacts is immutable, and the version you depend on will always be exactly the same thing

2021-01-16T04:21:02.281900Z

Then with git deps using shas, that covers your own code base.

2021-01-16T04:21:41.282100Z

But short of being sure that your deps.edn deps are guaranteed to be the same every time you pull them down, for the same version you specified, it could get you in trouble.

2021-01-16T04:22:22.282300Z

That and, if you depend on public repos too, like Clojars, that can go down at any time, someone can pull out their lib from it, or someone could take it over and replace some of the lib their with compromised ones

2021-01-16T04:24:27.282700Z

That said... I never thought about this, but maybe if you packaged the clj local deps and classpath cache, and replicated that across your servers, but that is actually exactly what an UberJar is, just a lot more convenient then this

2021-01-16T04:25:14.283Z

Like the act of creating an UberJar is just taking all your dependencies and your code and putting it in a Zip file.

2021-01-16T04:25:44.283200Z

Just one you don't have to unzip, and can just launch your app straight from the zip

Steven Lombardi 2021-01-16T05:54:31.283500Z

transducers clicked for me after reading rich's history of clojure paper https://dl.acm.org/doi/pdf/10.1145/3386321

Steven Lombardi 2021-01-16T05:54:35.283700Z

there's some good info in there

roelof 2021-01-16T10:01:15.285700Z

good morning all

roelof 2021-01-16T10:11:29.286700Z

Why does this fail on 4clojure

#(and (contains? %1 %2) (nil? (get %1 %2)))
tests :
Write a function which, given a key and map, returns true iff the map contains an entry with that key and its value is nil.

(true?  (__ :a {:a nil :b 2}))

(false? (__ :b {:a nil :b 2}))

(false? (__ :c {:a nil :b 2}))

Antonio Bibiano 2021-01-16T10:13:40.288Z

what error do you get? I remember trying to do something similar and it was not possible to use and like that because it's a macro

Antonio Bibiano 2021-01-16T10:14:15.288200Z

maybe the (fn ..) syntax will work

roelof 2021-01-16T10:14:18.288400Z

no, error on the 4clojure site only a message that I fail the tests

roelof 2021-01-16T10:21:44.288600Z

yep, this is working

(fn test [ key map] 
  (and (contains? map key) (nil? (get map key)))) 

Antonio Bibiano 2021-01-16T10:24:13.288800Z

yeah actually i think i was wrong

Antonio Bibiano 2021-01-16T10:24:26.289Z

you just mixed the order of the arguments

Antonio Bibiano 2021-01-16T10:24:35.289200Z

in you first definiton

Antonio Bibiano 2021-01-16T10:24:45.289400Z

this works

Antonio Bibiano 2021-01-16T10:24:51.289600Z

(true?  (#(and (contains? %2 %1) (nil? (get %2 %1))) :a {:a nil :b 2}))

roelof 2021-01-16T10:27:22.289800Z

oops

roelof 2021-01-16T10:34:30.290400Z

is this a good clojure solution

When retrieving values from a map, you can specify default values in case the key is not found:

(fn test[default sequence] 
   (into {} (for [x sequence] {x default})))

jumar 2021-01-16T10:37:51.290900Z

You can use find too

Antonio Bibiano 2021-01-16T10:38:37.291500Z

i also found that

Antonio Bibiano 2021-01-16T10:39:54.291700Z

#(nil? (get %2 %1 "nope")) works but it's not really obvious what one should use in the default case

roelof 2021-01-16T10:42:14.292Z

yep, i agree. the challenge is not clear about it

Antonio Bibiano 2021-01-16T10:43:48.292200Z

I think the challenge is clear, and using a default value for the get i think makes sense, you just have to put something that is not nil as the default, but i'm not sure what would be the best option

roelof 2021-01-16T11:12:28.292500Z

oke

2021-01-16T13:25:26.294Z

How about : #(nil? (get %2 %1 :else))

Christian 2021-01-16T14:53:52.297Z

Something basic to see if I understand correctly. If I want to use a non-standard library: • how to I see it's not standard? doesn't start with "clojure/"? • Do I always have to state my intent in two places? With leinigen in the project.clj, so Leinigen can download it and make it available and in the namespace again so I can use it in the namespace?

2021-01-16T15:07:58.297100Z

I think "standard library" is the content of clojure.core namespace. Everything else is a non-standard. And yes to your second question in case of leiningen

2021-01-16T15:09:33.297300Z

As for the second point, yes, but it’s helpful to note that there are two intentions here: expressing what you intend to use (a concrete version of a library), and where you intend to use it (in this or that ns). You express the first intention to your deps management tool (project.clj for lein, deps.edn for clj, etc), and express the second in the namespace itself.

popeye 2021-01-16T15:14:35.298700Z

Why do we need to use partial function? We can achieve it using normal defn right? why we go for partial ?

2021-01-16T15:43:14.300Z

There is never a time where partial can be used, that one cannot use (fn ...) or sometimes #( ...) instead. So in that sense there is no need to use partial. Some people prefer partial when it does the job.

🙌 2
roelof 2021-01-16T16:14:45.300200Z

nope, it fails the first case

(= (__ 0 [:a :b :c]) {:a 0 :b 0 :c 0})

roelof 2021-01-16T17:04:44.301Z

I have solved this challenge : https://www.4clojure.com/problem/156

(fn test[default sequence] 
   (into {} (for [x sequence] {x default})))

roelof 2021-01-16T17:05:00.301500Z

is that good clojure code or can I improve it somehow ?

Joe 2021-01-16T17:16:49.301900Z

Looks good to me. An alternative would be

(fn [default ks] 
  (zipmap ks (repeat default)))
Maybe be careful with the names - sequence and test are both already core functions.

👍 1
2021-01-16T17:18:28.302100Z

Good warnings on names. Although, the shorter the function, the less likely such shadowing of names is to cause problems, and that is a nice short function.

roelof 2021-01-16T17:48:18.302400Z

thanks both

roelof 2021-01-16T17:48:35.302600Z

so ks is a better name for a collection

roelof 2021-01-16T17:48:42.302800Z

or maybe col ?

Joe 2021-01-16T17:51:12.303400Z

Most commonly you will see xs (as in "a collection of x's") to refer to a generic collection of things.

roelof 2021-01-16T17:51:44.303600Z

oke, i know that one from my haskell days

👌 1
roelof 2021-01-16T17:52:10.303900Z

there is xs also often used

roelof 2021-01-16T17:58:54.304800Z

anyone xp with clj.http to make this work instead of slurp `(slurp "https://www.rijksmuseum.nl/api/nl/collection?key=14OGzuak&format=json&type=schilderij&toppieces=True") ?

clyfe 2021-01-16T18:07:13.304900Z

(client/get "<https://www.rijksmuseum.nl/api/nl/collection>"
            {:query-params {:key "14OGzuak" 
                            :format "json"
                            :type "schilderij"
                            :toppieces "True"}})

roelof 2021-01-16T18:08:32.305100Z

vrey quick answer

roelof 2021-01-16T18:09:25.305300Z

I can use the same trick here

roelof 2021-01-16T18:09:29.305500Z

(str "<https://www.rijksmuseum.nl/api/nl/collection/>" object-number "/tiles?key=14OGzuak&amp;format=json")

roelof 2021-01-16T18:09:56.305900Z

where the objectnumber is a variable from a loop

clyfe 2021-01-16T18:10:09.306300Z

yes

roelof 2021-01-16T18:10:39.306500Z

oke, how do I handle the objectnumber as variable then ?

popeye 2021-01-16T18:10:50.306800Z

(some #(contains? % "xx") #{"xx" "yy" "jj"})

popeye 2021-01-16T18:10:59.307100Z

this is giving me an issue

clyfe 2021-01-16T18:11:19.307400Z

(for [on ons] (use on))

popeye 2021-01-16T18:11:20.307600Z

but this is returning result (contains? #{"xx" "yy" "jj"} "xx")

popeye 2021-01-16T18:11:34.308Z

is there any issue with the 1st function?

roelof 2021-01-16T18:11:49.308100Z

that not what I mean

pavlosmelissinos 2021-01-16T18:13:18.308300Z

Have you seen the examples in the docs? https://github.com/dakrone/clj-http#get

clyfe 2021-01-16T18:13:41.308900Z

(client/get
 (str "<https://www.rijksmuseum.nl/api/nl/collection/>" object-number "/tiles")
 {:query-params ...})

roelof 2021-01-16T18:16:26.309400Z

so something like this :

(client/get "<https://www.rijksmuseum.nl/api/nl/??/tiles>"
            {:query-params {:key "14OGzuak"
                            :format "json"
                            :object-number :object-number"}})

clyfe 2021-01-16T18:18:04.309700Z

1st fn, 1st contains call: (contains? "xx" "xx") => contains? not supported on type: java.lang.String

Joe 2021-01-16T18:18:12.309900Z

I think it will clarify if you unpack the expression. What is #(contains? % "xx") being applied to in the first case?

popeye 2021-01-16T18:19:44.310300Z

i am passing set in place of %

pavlosmelissinos 2021-01-16T18:20:34.310500Z

Is object-number part of the query string?

roelof 2021-01-16T18:21:00.310900Z

yep, is it a variable part

clyfe 2021-01-16T18:23:05.311100Z

Seen my answer there yes?

roelof 2021-01-16T18:24:20.311400Z

@claudius.nicolae yep

clyfe 2021-01-16T18:24:22.311600Z

Noting: query-params it's what's after the "?" bit.

☝️ 1
roelof 2021-01-16T18:24:38.311900Z

so I can just use it as a variable

clyfe 2021-01-16T18:24:43.312100Z

the url path you concatenate, yes

Joe 2021-01-16T18:25:05.312300Z

Understandable you would think that, but it is not the case here. Check out the docs for https://clojuredocs.org/clojure.core/some

clyfe 2021-01-16T18:25:28.312500Z

https://github.com/metosin/reitit can do "reverse routing": template + path params = http path

popeye 2021-01-16T18:26:27.312800Z

I am trying to do same as

(some #(= 5 %) [6 7 8 9 10])

popeye 2021-01-16T18:26:36.313Z

which is mentioned in document

clyfe 2021-01-16T18:28:43.313500Z

(def router (r/router ["/api"
                       ["/foo" ::foo]
                       ["/bar/:id" ::bar]]))

(:path (r/match-by-name router ::foo)) ;; /api/foo
(:path (r/match-by-name router ::bar {:id 1})) ;; /api/bar/1

roelof 2021-01-16T18:30:02.313900Z

oke, I did not learn reitit

popeye 2021-01-16T18:30:06.314100Z

or, is it just taking first element from set?

roelof 2021-01-16T18:30:12.314400Z

Just compujure and ring so far

Joe 2021-01-16T18:30:25.314900Z

(some pred coll)

Returns the first logical true value of (pred x) for any x in coll,
else nil. 
So you are applying #(contains? % "xx") to every element of the set #{"xx" "yy" "jj"} Not the set itself. In other words, (contains? "xx" "xx") as clyfe mentions.

popeye 2021-01-16T18:30:56.315300Z

yeah...

popeye 2021-01-16T18:31:44.315500Z

(some #(= % "xx") #{"xx" "yy" "jj"}) worked

👍 1
popeye 2021-01-16T18:35:37.315800Z

can we use = in comparing string in clojure?

popeye 2021-01-16T18:36:18.316Z

does it check string content ? or reference?

paulocuneo 2021-01-16T18:37:15.316200Z

maybe https://clojuredocs.org/clojure.string/includes_q is what you want?

popeye 2021-01-16T18:37:51.316400Z

include does not suit my requitement

popeye 2021-01-16T18:38:06.316600Z

want to check exact string

paulocuneo 2021-01-16T18:38:13.316800Z

= then

Joe 2021-01-16T18:38:23.317Z

> can we use = in comparing string in clojure? I think in general, yeah you're pretty safe doing that

popeye 2021-01-16T18:38:41.317200Z

does it check the string content?

clyfe 2021-01-16T18:38:48.317400Z

yes

popeye 2021-01-16T18:39:56.317600Z

(defn =
  "Equality. Returns true if x equals y, false if not. Same as
  Java x.equals(y) except it also works for nil, and compares
  numbers and collections in a type-independent manner.  Clojure's immutable data
  structures define equals() (and thus =) as a value, not an identity,
  comparison."

popeye 2021-01-16T18:40:05.317800Z

yeah this is i got from core code

popeye 2021-01-16T18:40:27.318Z

thanks

Claudio Ferreira 2021-01-16T19:03:50.321400Z

Ppl, what is the purpose of using a new argument with reduce in this example? What he does? Thank you!

seancorfield 2021-01-16T19:15:26.322500Z

@claudioferreira.dev If you don't provide the "initial" value in reduce, it has some slightly strange semantics. Take a close look at the docstring.

seancorfield 2021-01-16T19:17:14.323200Z

"If val is not supplied, returns the result of applying f to the first 2 items in coll, then applying f to that result and the 3rd item, etc." -- That's fine. But pay attention to this bit: "If coll contains no items, f must accept no arguments as well, and reduce returns the result of calling f with no arguments."

seancorfield 2021-01-16T19:18:47.323700Z

And you you have this case (again, when "initial" value is not provided): "If coll has only 1 item, it is returned and f is not called."

seancorfield 2021-01-16T19:20:24.325100Z

For example:

user=&gt; (reduce println [1])
1 ; println not called, returns first (only) value of the collection
user=&gt; (reduce println [] [1])
[] 1 ; println called -- prints initial value and first element
nil ; returns nil because that's what println produces

Sebastian Cheung 2021-01-16T19:22:10.327200Z

Hi I can totally replace ClojureScript inside an existing React client project?

seancorfield 2021-01-16T19:22:12.327300Z

And then with no elements in the collection:

user=&gt; (reduce println [])
 ; println is called here with no arguments
nil ; returned from calling println
user=&gt; (reduce println [] [])
[] ; doesn't call println, returns initial value
The latter is easier to see with (reduce println [1] []) which just returns [1]

seancorfield 2021-01-16T19:22:30.327700Z

Does that help @claudioferreira.dev?

Claudio Ferreira 2021-01-17T10:57:38.437700Z

Yeah, thats make sense about the Ireduce problem.

Claudio Ferreira 2021-01-17T11:21:47.441200Z

Thank you @seancorfield!!!! Now i understand this example. I appreciate your patience and attention

Sebastian Cheung 2021-01-16T19:23:11.329100Z

How does ClojureScript handle state management inside an existing React project then?

seancorfield 2021-01-16T19:24:18.330300Z

@sebastian_cheung Reagent and Om are ClojureScript libraries that wrap React.js under the hood. re-frame is built on top of Reagent to provide a structured way to manage state changes. You should look at the docs for those projects.

seancorfield 2021-01-16T19:25:04.331300Z

Essentially you would replace all your JS (and React.js usage) with a complete, new ClojureScript app that used re-frame, or Reagent alone, or Om etc

Sebastian Cheung 2021-01-16T19:26:41.333100Z

thanks @seancorfield but I will be working in a team, they will not change their React code just for me, so only potentially ClojureScript from my side, interfacing with their React features

seancorfield 2021-01-16T19:27:26.334100Z

@sebastian_cheung My understanding is that is not possible. cljs assumes "whole program".

phronmophobic 2021-01-16T19:27:34.334300Z

There are several clojurescript projects that work well with React (including react and re-frame). I wouldn't suggest Om for new projects though. From Om's Readme: > NOTE: This project is no longer under active development. If you'd like to use a library that's well maintained that was inspired by some of the ideas presented here see https://github.com/fulcrologic/fulcro

seancorfield 2021-01-16T19:28:18.335100Z

Glad to hear that Om is directing users to Fulcro. Does the same apply to Om.Next?

phronmophobic 2021-01-16T19:28:36.335200Z

Yes, afaik

phronmophobic 2021-01-16T19:29:35.336200Z

I believe using clojurescript to interop with existing react code is possible. I think https://github.com/lilactown/helix might be a good library to start with for that use case.

phronmophobic 2021-01-16T19:30:33.336900Z

per its https://github.com/lilactown/helix/blob/master/docs/motivation.md: > The goals of Helix are: > - Provide an ergonomic, well-documented API for building components in ClojureScript > - Have as small of a runtime as possible > - Add as few new semantics on top of React as possible

Sebastian Cheung 2021-01-16T19:30:44.337400Z

Fulcro or helix then?

seancorfield 2021-01-16T19:30:59.337500Z

Hmm, that repo is where I end up if I follow links for Om Next so I guess both versions of Om have gone away. Can't say I liked it, compared to Reagent -- at least when we looked at apps built with both Om and Reagent back in 2013/2014 🙂

seancorfield 2021-01-16T19:33:32.339400Z

@smith.adriane Reading the docs for Helix, it's not clear to me that you could use it to define React components/hooks and use it as part of an existing JS React.js app?

phronmophobic 2021-01-16T19:34:14.340600Z

I've been trying to escape the tyranny of the browser. I was able to get Fulcro to work on desktop. re-frame has implicit dependencies on react. I think that may be the case with reagent as well.

seancorfield 2021-01-16T19:34:21.340900Z

@sebastian_cheung I think Fulcro is also going to assume that it's in charge of the whole app too, like re-frame/Reagent...?

2021-01-16T19:34:30.341Z

Couldn’t the CLJS be compiled to an npm module & required by the JS? I’ve never done it, but these docs look promising: https://shadow-cljs.github.io/docs/UsersGuide.html#target-npm-module

phronmophobic 2021-01-16T19:37:16.343100Z

maybe @lilactown could provide better clarification. from this https://github.com/lilactown/helix/blob/master/docs/creating-components.md#interop : > One thing to note is that this conversion of JS objects to CLJS data types is shallow; this means if you pass in data like a JS object, array, etc. to a prop, it will be left alone. > > This is an intentional design decision. It is a tradeoff - on the one hand, it is more efficient to opt not to deeply convert JS data structures to CLJS data, and it means that you do not need to learn some Helix-specific rules when interoping with external React libraries that use higher-order components or render props.

seancorfield 2021-01-16T19:37:33.343300Z

Oh, that does look like it might help @sebastian_cheung build stuff with cljs and integrate it into an existing React JS app?

roelof 2021-01-16T19:38:14.344Z

see that web development is now a lot about react and sons

phronmophobic 2021-01-16T19:38:27.344200Z

also: > Helix's philosophy is to give you a Clojure-friendly API to raw React. All Helix components are React components, and vice-versa; any external React library can be used with Helix with as minimal interop ceremony as possible.

seancorfield 2021-01-16T19:38:52.344300Z

re-frame can be used server-side in Clojure -- it's kind of weird but it's possible...

phronmophobic 2021-01-16T19:40:07.344600Z

I meant for building desktop apps.

seancorfield 2021-01-16T19:42:19.344800Z

With... Electron or something similar?

phronmophobic 2021-01-16T19:43:27.345Z

my side project is trying 🤞 to clojurize UI development: https://github.com/phronmophobic/membrane

phronmophobic 2021-01-16T19:44:08.345300Z

most of the cool UI development is happening in cljs with react in mind

phronmophobic 2021-01-16T19:44:55.345500Z

but where possible, I've tried to use existing UI state libraries without react

roelof 2021-01-16T19:46:05.346200Z

so mayb in the futuure look for a coure how to make a html site into a react site

seancorfield 2021-01-16T19:48:37.348300Z

I'm going through a lot of material and courses right now about getting started with re-frame -- SPAs that make API calls to Clojure on the backend.

octahedrion 2021-01-16T19:48:44.348400Z

is recurring implicitly from tail position the same as using recur or must you explictly use recur?

seancorfield 2021-01-16T19:49:53.349300Z

@octo221 You have to use recur explicitly. Otherwise it is going to use the stack for recursion and you may blow the stack.

seancorfield 2021-01-16T19:50:20.349900Z

recur is explicit so that you can only use it in a position where it can avoid using the stack.

seancorfield 2021-01-16T19:50:53.351Z

(if you try to use it in a non-tail position, you'll get a compiler error)

octahedrion 2021-01-16T19:51:02.351100Z

thank you @seancorfield

octahedrion 2021-01-16T19:51:47.352100Z

but if the compiler can give error when`recur`is used in non-tail position, why can't it replace implicit recur with recur automatically ?

octahedrion 2021-01-16T19:52:07.352400Z

(when called from tail position)

seancorfield 2021-01-16T19:53:23.353700Z

That's a design decision that Rich has talked about. He wanted folks to be able to look at code and immediately tell whether it was doing actual recursion or the stack-friendly "tail call optimization", i.e., recur.

octahedrion 2021-01-16T19:53:46.354Z

I see

octahedrion 2021-01-16T19:54:22.354900Z

yes I see that makes good sense

seancorfield 2021-01-16T19:54:30.355100Z

When we see recur we know it's "safe" and using the optimized form: looping with new bindings rather than actually doing recursive calls.

seancorfield 2021-01-16T19:55:16.356100Z

When we see a recursive function call, we know it's using the stack. So it makes it simple to see what's going on -- and not have to try to discern what the compiler will do with our code behind the scenes.

octahedrion 2021-01-16T19:56:12.356400Z

good explanation thanks

roelof 2021-01-16T20:14:20.357Z

@seancorfield do you have then a good course for me to learn re-frame ?

roelof 2021-01-17T10:40:11.437500Z

oke, I think i try the free course first. Find 200 till 1000 euro very much for a hobby

2021-01-16T20:23:54.359100Z

I also think having implicit recur would give you the false impression that the compiler can detect mutually recursive tail calls, which it cannot. By forcing the use of recur you know it's not possible for you to recurse to another function that will call you back.

roelof 2021-01-16T20:28:53.359900Z

how can I convert keys here to keywords :

`-&gt;&gt;(client/get "<https://www.rijksmuseum.nl/api/nl/collection>"
                                          {:query-params {:key "14OGzuak"
                                                          :format "json"
                                                          :type "schilderij"
                                                          :toppieces "True"}})
           (:body)
           (json/parse-string)))

pavlosmelissinos 2021-01-16T20:34:51.360Z

https://clojuredocs.org/clojure.walk/keywordize-keys

lilactown 2021-01-16T20:38:10.360400Z

@smith.adriane @seancorfield @sebastian_cheung it's 💯 possible to use components defined using helix in a ReactJS app. care has to be taken to accept props in a way that is ergonomic for the consumer, though. E.g. you probably won't want to expect that someone using your components are passing in ClojureScript maps, vectors, sets etc.

lilactown 2021-01-16T20:38:43.360600Z

it's also possible to do this with Fulcro, it requires more steps and care though

lilactown 2021-01-16T20:40:21.360800Z

personally, I would not try and develop a bunch of components in CLJS that are then used in a React app, as the tradeoffs are not worth it. ClojureScript is a good application language. You'll find that when creating libraries that are then used in JS, those libraries are harder to build, to use, and will have worse performance than a JS lib

lilactown 2021-01-16T20:42:13.361Z

I have had great success going the other direction, building components in JS that I then use in CLJS. Helix had that in mind when it was built, and allows you to use ReactJS components basically the same as if you defined them using Helix

👍 1
seancorfield 2021-01-16T20:44:08.361200Z

Just pass the right options to json/parse-string

seancorfield 2021-01-16T20:45:13.361400Z

This is data.json right? It's right there in the README @roelof: https://github.com/clojure/data.json#converting-keyvalue-types

seancorfield 2021-01-16T20:49:30.361800Z

Thanks for the follow-up @lilactown -- that sort of confirms the impression that I'd gotten about cljs vs JS (use cljs for the app, JS for interop/components as needed) but interesting to hear that you can write components in cljs and use them from JS/React.js but it has caveats...

roelof 2021-01-16T20:49:37.362Z

nope, this is cheshire

seancorfield 2021-01-16T20:50:08.362200Z

Then read Cheshire's docs -- that readme also covers this.

roelof 2021-01-16T20:50:36.362500Z

think I gave up on clojure

(json/parse-string (client/get
                           (str "<https://www.rijksmuseum.nl/api/nl/collection/>" object-number "/tiles")
                           {:query-params {:key "14OGzuak"
                                           :format "json"}}))))
error: 

; Execution error (ClassCastException) at cheshire.core/parse-string (core.clj:207).
; class clojure.lang.PersistentHashMap cannot be cast to class java.lang.String (clojure.lang.PersistentHashMap is in unnamed module of loader 'app'; java.lang.String is in module java.base of loader 'bootstrap')

seancorfield 2021-01-16T20:50:42.362700Z

https://github.com/dakrone/cheshire#decoding

seancorfield 2021-01-16T20:51:50.363100Z

@roelof look at what client/get returns -- try it in the REPL.

seancorfield 2021-01-16T20:51:58.363300Z

It is not a string. It's a hash map.

roelof 2021-01-16T20:52:20.363500Z

chips, I see it

seancorfield 2021-01-16T20:52:26.363700Z

parse-string expects a string and you're handing it a hash map.

roelof 2021-01-16T20:52:38.363900Z

never code just before goto sleep

seancorfield 2021-01-16T20:52:51.364100Z

You need to learn to take more careful steps and try things out in the REPL.

roelof 2021-01-16T20:56:13.364300Z

oke

roelof 2021-01-16T20:56:26.364500Z

do you like this code :

(ns paintings.core
  (:require [cheshire.core :as json]
            [clj-http.client :as client]
            [clojure.walk :as walk]))


(defn image-url-size [image]
  (let [data (select-keys image [:width :height])
        url (get-in image [:tiles 0 :url])]
    (assoc data :url url)))

(defn assoc-image [object-number]
  (-&gt;&gt; (client/get (str "<https://www.rijksmuseum.nl/api/nl/collection/>" object-number "/tiles")
                           {:query-params {:key "14OGzuak"
                                           :format "json"}})
       (:body)
       (json/parse-string)
       (walk/keywordize-keys)
       (:levels)
       (sort-by :name)
       (last)
       (image-url-size)
       (merge {:object-number object-number})))

(time (-&gt;&gt;(client/get "<https://www.rijksmuseum.nl/api/nl/collection>"
                                          {:query-params {:key "14OGzuak"
                                                          :format "json"
                                                          :type "schilderij"
                                                          :toppieces "True"}})
           (:body)
           (json/parse-string)
           (walk/keywordize-keys)
           (:artObjects)
           (map :objectNumber)
           (map assoc-image)))

seancorfield 2021-01-16T20:59:50.364800Z

Like I just said, you don't need keywordize-keys. Just tell Cheshire to return keywords.

roelof 2021-01-16T21:00:50.365Z

yep but then I think I cannot use -&gt;&gt; but then I have to use -&gt;

seancorfield 2021-01-16T21:01:27.365200Z

Be careful about using time around a map expression because map is lazy.

seancorfield 2021-01-16T21:01:51.365400Z

You can use -&gt; for most of the pipeline and then switch to -&gt;&gt; for the final steps.

roelof 2021-01-16T21:02:26.365600Z

can I ? never seen that

seancorfield 2021-01-16T21:02:26.365800Z

But it's considered poor style to mix threading types -- because it indicates you are changing from "object/thing/collection" functions to sequence functions.

seancorfield 2021-01-16T21:02:39.366Z

Can I what?

roelof 2021-01-16T21:03:07.366200Z

I learned I have to use a function to use -&gt; in a -&gt;&gt; pipeline

seancorfield 2021-01-16T21:03:31.366400Z

(-&gt; (client/get ..) :body (json/parse-string true) :artObjects (-&gt;&gt; (map :objectNumber) (map assoc-image)))

roelof 2021-01-16T21:03:56.366600Z

okek but that is a poort style

roelof 2021-01-16T21:04:00.366800Z

so better not use it

seancorfield 2021-01-16T21:04:46.367Z

Poor style because you're trying to do too much in a single pipeline. Refactor it into small functions to transform the data.

seancorfield 2021-01-16T21:05:07.367200Z

And don't forget about my comment about time and map.

roelof 2021-01-16T21:05:15.367400Z

I do

roelof 2021-01-16T21:05:44.367600Z

I will remember that the outcome is not the real time

seancorfield 2021-01-16T21:05:48.367800Z

time will just tell you how long it took to construct the initial lazy seq, not how long it actually takes to do the transformation work.

roelof 2021-01-16T21:05:55.368Z

oke

roelof 2021-01-16T21:06:07.368200Z

then I will delete it

seancorfield 2021-01-16T21:06:25.368400Z

Compare (time (mapv inc (range 1000))) and (time (map inc (range 1000)))

roelof 2021-01-16T21:06:26.368600Z

I was hoping it would say how long it all took

seancorfield 2021-01-16T21:07:25.368800Z

(time (doall (map ..))) if you must use map. Or use an eager process like mapv.

roelof 2021-01-16T21:08:06.369Z

the map is much faster then the mapv one

Claudio Ferreira 2021-01-16T21:09:51.369200Z

Kinda! @seancorfield I understood that relation between f, val and coll. But i cannot understand how can that be useful in our day by day. Which problem does adding " { } " (just like in the pic i sent initially) as a initial value solves or facilitate our life? e.g

(reduce + [1 2 3])
=&gt; 6 ; Great, we added all the values together

(reduce + {} [1 2 3]) ; ??? "{ }" helps nothing here
Execution error (ClassCastException)...

seancorfield 2021-01-16T21:10:27.369400Z

In this? "Compare (time (mapv inc (range 1000))) and (time (map inc (range 1000)))" -- no, that's exactly the point I was making: time of map doesn't time the work.

roelof 2021-01-16T21:10:52.369600Z

oke

roelof 2021-01-16T21:10:58.369800Z

I see a big difference

roelof 2021-01-16T21:11:03.370Z

that is all I mean

seancorfield 2021-01-16T21:11:52.370200Z

Right, because (time (map inc (range 1000))) is not timing how long it takes to map inc over that range.

seancorfield 2021-01-16T21:12:22.370400Z

It's just timing how long it takes to create an (unrealized) lazy sequence object -- it isn't doing any work.

roelof 2021-01-16T21:12:43.370600Z

im now trying to figure out why this is not working

(-&gt;&gt; (client/get "<https://www.rijksmuseum.nl/api/nl/collection>"
                       {:query-params {:key "14OGzuak"
                                       :format "json"
                                       :type "schilderij"
                                       :toppieces "True"}})
           :body 
           (json/parse-string true) 
           :artObjects 
           (-&gt;&gt; (map :objectNumber) 
                (map assoc-image)))

seancorfield 2021-01-16T21:12:48.370800Z

(time (doall (map inc (range 1000)))) will force realization of the mapping.

roelof 2021-01-16T21:13:01.371Z

oke

seancorfield 2021-01-16T21:13:13.371200Z

You're using -&gt;&gt; at the beginning, not -&gt;

Claudio Ferreira 2021-01-16T21:13:46.371400Z

If so, why did the exercise added { } inside that maximum and minimum function? Just for the result be in a map? The book used "accumulator" to define that { }. So, we are taking our values from the coll [5 23 5004 845 22] and putting it inside the "acumulator"? (or "{}"/initial value)

seancorfield 2021-01-16T21:13:52.371600Z

It should be (-&gt; (client/get ..) ... (-&gt;&gt; (map ..) (map ..)))

roelof 2021-01-16T21:13:54.371800Z

time for me to sleep

roelof 2021-01-16T21:14:00.372200Z

but the code is working

seancorfield 2021-01-16T21:14:23.372400Z

Now you've fixed it 🙂 Do you understand what you did wrong?

Claudio Ferreira 2021-01-16T21:14:58.372600Z

Sorry for the dumb question @seancorfield , have been thinking for hours about this initial value but still didn't found the answer. Thanks for the support!

seancorfield 2021-01-16T21:15:36.372800Z

user=&gt; (reduce + [1 2 3])
6
user=&gt; (reduce + 0 [1 2 3])
6
user=&gt; (reduce + 13 [1 2 3])
19

Claudio Ferreira 2021-01-16T21:16:07.373Z

Hmmmm, just like partial?

roelof 2021-01-16T21:18:02.373400Z

yep

roelof 2021-01-16T21:18:10.373600Z

again I want to fast

roelof 2021-01-16T21:18:16.373800Z

very bad old habit

roelof 2021-01-16T21:18:33.374Z

but this is better style

(defn take-data[image-data]
  (-&gt;&gt; image-data
      (map :objectNumber)
      (map assoc-image)))

(-&gt; (client/get "<https://www.rijksmuseum.nl/api/nl/collection>"
                       {:query-params {:key "14OGzuak"
                                       :format "json"
                                       :type "schilderij"
                                       :toppieces "True"}})
           :body 
           (json/parse-string true) 
           :artObjects
           (take-data))

seancorfield 2021-01-16T21:19:09.374400Z

Better, yes.

roelof 2021-01-16T21:19:38.374600Z

oke, tomorrow I try to rewrite this part

roelof 2021-01-16T21:20:07.374800Z

(defn assoc-image [object-number]
  (-&gt;&gt; (client/get (str "<https://www.rijksmuseum.nl/api/nl/collection/>" object-number "/tiles")
                   {:query-params {:key "14OGzuak"
                                   :format "json"}})
       (:body)
       (json/parse-string true)
       (:levels)
       (sort-by :name)
       (last)
       (image-url-size)
       (merge {:object-number object-number})))

roelof 2021-01-16T21:20:14.375Z

the same way

seancorfield 2021-01-16T21:20:15.375200Z

No.

seancorfield 2021-01-16T21:21:40.375400Z

Compare these:

user=&gt; (reduce conj [] [1 2 3 4])
[1 2 3 4]
user=&gt; (reduce conj {} [1 2 3 4])
Execution error (IllegalArgumentException) at user/eval173 (REPL:1).
Don't know how to create ISeq from: java.lang.Long
user=&gt; (reduce conj #{} [1 2 3 4])
#{1 4 3 2}
user=&gt; (reduce conj [] {:a 1, :b 2, :c 3})
[[:a 1] [:b 2] [:c 3]]
user=&gt; (reduce conj {} {:a 1, :b 2, :c 3})
{:a 1, :b 2, :c 3}
user=&gt; (reduce conj #{} {:a 1, :b 2, :c 3})
#{[:c 3] [:b 2] [:a 1]}

seancorfield 2021-01-16T21:22:53.376200Z

That's wrong.

roelof 2021-01-16T21:23:07.376400Z

???

roelof 2021-01-16T21:23:16.376600Z

I know that it's wrong

seancorfield 2021-01-16T21:23:24.376800Z

You're using -&gt;&gt; again and you need -&gt;, and then switch to -&gt;&gt; later in the pipeline

roelof 2021-01-16T21:23:37.377Z

that what I rewrite it as you learn me afew moments ago

seancorfield 2021-01-16T21:23:45.377200Z

Oh, you mean you need to rewrite it "the same way" as you rewrote the other code?

seancorfield 2021-01-16T21:23:56.377400Z

Sorry, it's hard to understand what you're asking at times.

roelof 2021-01-16T21:24:08.377600Z

that is what I try to say that I will try to make that work tommorow

roelof 2021-01-16T21:24:50.377800Z

sorry, english is not my mother languages and I was years and years ago very bad in languages

roelof 2021-01-16T21:24:54.378Z

at school

Claudio Ferreira 2021-01-16T21:26:04.378600Z

But, why does the values here go "back" to the initial value if we are not using conj?

(reduce (fn [{:keys [minimum maximum]} new-number]
          {:minimum (if (and minimum (&gt; new-number minimum))
                      minimum
                      new-number)
           :maximum (if (and maximum (&lt; new-number maximum))
                      maximum
                      new-number)})
        {}   ;; &lt;---- The new argument!
        [5 23 5004 845 22])
{:minimum 5, :maximum 5004} 
And here they dont: (reduce + { } [1 2 3]) Execution error (ClassCastException) => {6} ; The result i expected

seancorfield 2021-01-16T21:26:23.379Z

NP. I get it now. Good luck tomorrow -- once you've had some sleep!

seancorfield 2021-01-16T21:26:37.379400Z

And remember: take small steps and try each piece out in the REPL.

seancorfield 2021-01-16T21:29:00.379600Z

In (reduce + {} [1 2 3]) the first step is (+ {} 1) which is not legal.

seancorfield 2021-01-16T21:29:53.379800Z

In (reduce + 13 [1 2 3]) the first step is (+ 13 1) => 14. The next step is (+ 14 2) => 16. The final step is (+ 16 3) => 19.

seancorfield 2021-01-16T21:31:05.380Z

In your min max example, the first step is to call that function with {} and 5 -- and the function destructures {} against {:keys [minimum maximum]} so both minimum and maximum are bound to nil.

seancorfield 2021-01-16T21:31:49.380200Z

And the if conditions check for nil: (and minimum (&gt; new-number minimum)) will guard the comparison for non-`nil` values.

seancorfield 2021-01-16T21:32:09.380400Z

So it produces {:minimum 5, :maximum 5}

seancorfield 2021-01-16T21:32:37.380600Z

The next step is to call that function with {:minimum 5, :maximum 5} and 23

seancorfield 2021-01-16T21:33:07.380800Z

So now we get {:minimum 5, :maximum 23} and go round again with 5004 and so on.

seancorfield 2021-01-16T21:33:10.381Z

Does that help?

seancorfield 2021-01-16T21:34:00.381200Z

(reduce + [1 2 3]) is the same as (reduce + 1 [2 3]) so it does (+ 1 2) and then (+ 3 3)

seancorfield 2021-01-16T21:34:30.381400Z

But (reduce + []) is going to call (+) -- with no arguments -- which produces 0

seancorfield 2021-01-16T21:34:49.381600Z

And (reduce + [1]) is just going to return 1 without calling +.

seancorfield 2021-01-16T21:35:41.381800Z

You can see that + is not called, in this example (reduce + [:not-a-number]) which produces :not-a-number (if it tried to call + on it, you'd get an exception).

roelof 2021-01-16T21:48:23.385Z

yep, could not let it wait till tomorrow

roelof 2021-01-16T21:48:35.385200Z

what do you think

(ns paintings.core
  (:require [cheshire.core :as json]
            [clj-http.client :as client]
            [clojure.walk :as walk]))


(defn image-url-size [image]
  (let [data (select-keys image [:width :height])
        url (get-in image [:tiles 0 :url])]
    (assoc data :url url)))

(defn take-image-data[image-data object-number]
  (-&gt;&gt; image-data
       (sort-by :name)
       (last)
       (image-url-size)
       (merge {:object-number object-number})))

(defn assoc-image [object-number]
  (-&gt; (client/get (str "<https://www.rijksmuseum.nl/api/nl/collection/>" object-number "/tiles")
                   {:query-params {:key "14OGzuak"
                                   :format "json"}})
       (:body)
       (json/parse-string true)
       (:levels)
       (take-image-data object-number)))

(defn take-data [api-data]
  (-&gt;&gt; api-data
       (map :objectNumber)
       (map assoc-image)))

(-&gt; (client/get "<https://www.rijksmuseum.nl/api/nl/collection>"
                {:query-params {:key "14OGzuak"
                                :format "json"
                                :type "schilderij"
                                :toppieces "True"}})
    :body
    (json/parse-string true)
    :artObjects
    (take-data))

🎉 1
roelof 2021-01-16T22:11:17.385500Z

GN all

seancorfield 2021-01-16T22:14:38.385600Z

Nice breakdown! Yay!

Claudio Ferreira 2021-01-16T22:35:47.387900Z

THANK YOU @seancorfield!!!! Finaaaally ive understand that!

roelof 2021-01-16T22:39:09.388100Z

oke, then now study ring and computure to make the back-end

roelof 2021-01-16T22:39:20.388300Z

and then decide what to use for the front-end

seancorfield 2021-01-16T22:45:59.389100Z

Cool. This higher-order stuff can be a bit bewildering at first because it's not how folks program in traditional languages.

seancorfield 2021-01-16T22:46:27.389300Z

In Clojure, using sequence and collection functions is far more common than writing loops, for example.

seancorfield 2021-01-16T22:47:37.389500Z

Rich Hickey has said several times that the IReduce form of reduce (without the initial value) was a bad idea and he wishes he'd only created the IReduceInit form -- with three arguments: the reducing function, the initial value, and the sequence.

seancorfield 2021-01-16T22:49:59.389700Z

When I added "reducible queries" to clojure.java.jdbc, I implemented both forms and it was a mistake. When I implemented next.jdbc, I very deliberately omitted the IReduce form.